* [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board
@ 2025-08-15 9:00 Corvin Köhne
2025-08-15 9:00 ` [PATCH v2 01/14] hw/timer: Make frequency configurable Corvin Köhne
` (14 more replies)
0 siblings, 15 replies; 29+ messages in thread
From: Corvin Köhne @ 2025-08-15 9:00 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Maydell, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block
From: Corvin Köhne <c.koehne@beckhoff.com>
Hi,
Beckhoff has build a board, called CX7200, based on the Xilinx Zynq A9
platform. This commit series adds the Beckhoff CX7200 as new board variant to
QEMU.
The emulation is able to successfully boot an CX7200 image. The image includes
some self tests executed on every boot. Only the cache self test fails due to
QEMU emulating the cache as always being coherent. The self tests include f.e.:
* Network
* Flash
* CCAT DMA + EEPROM [1]
* TwinCAT (Beckhoff's automation control software [2])
[1] https://github.com/beckhoff/ccat
[2] https://www.beckhoff.com/en-us/products/automation/
YannickV (14):
hw/timer: Make frequency configurable
hw/timer: Make PERIPHCLK period configurable
hw/dma/zynq-devcfg: Handle bitstream loading via DMA to 0xffffffff
hw/arm/zynq-devcfg: Prevent unintended unlock during initialization
hw/dma/zynq: Ensure PCFG_DONE bit remains set to indicate PL is in
user mode
hw/dma/zynq-devcfg: Simulate dummy PL reset
hw/dma/zynq-devcfg: Indicate power-up status of PL
hw/dma/zynq-devcfg: Fix register memory
hw/misc: Add dummy ZYNQ DDR controller
hw/misc/zynq_slcr: Add logic for DCI configuration
hw/misc: Add Beckhoff CCAT device
hw/block/m25p80: Add HAS_SR_TB flag for is25lp016d
hw/arm: Add new machine based on xilinx-zynq-a9 for Beckhoff CX7200
docs/system/arm: Add support for Beckhoff CX7200
docs/system/arm/beckhoff-cx7200.rst | 57 ++++
docs/system/target-arm.rst | 1 +
hw/arm/Kconfig | 18 ++
hw/arm/beckhoff_CX7200.c | 440 ++++++++++++++++++++++++++++
hw/arm/meson.build | 1 +
hw/block/m25p80.c | 3 +-
hw/dma/xlnx-zynq-devcfg.c | 29 +-
hw/misc/Kconfig | 6 +
hw/misc/beckhoff_ccat.c | 365 +++++++++++++++++++++++
hw/misc/meson.build | 2 +
hw/misc/xlnx-zynq-ddrc.c | 393 +++++++++++++++++++++++++
hw/misc/zynq_slcr.c | 31 ++
hw/timer/a9gtimer.c | 25 +-
hw/timer/arm_mptimer.c | 33 ++-
include/hw/misc/xlnx-zynq-ddrc.h | 140 +++++++++
include/hw/timer/a9gtimer.h | 2 +
include/hw/timer/arm_mptimer.h | 4 +
17 files changed, 1538 insertions(+), 12 deletions(-)
create mode 100644 docs/system/arm/beckhoff-cx7200.rst
create mode 100644 hw/arm/beckhoff_CX7200.c
create mode 100644 hw/misc/beckhoff_ccat.c
create mode 100644 hw/misc/xlnx-zynq-ddrc.c
create mode 100644 include/hw/misc/xlnx-zynq-ddrc.h
--
2.50.1
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v2 01/14] hw/timer: Make frequency configurable
2025-08-15 9:00 [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Corvin Köhne
@ 2025-08-15 9:00 ` Corvin Köhne
2025-08-19 16:37 ` Peter Maydell
2025-08-19 16:41 ` Peter Maydell
2025-08-15 9:01 ` [PATCH v2 02/14] hw/timer: Make PERIPHCLK period configurable Corvin Köhne
` (13 subsequent siblings)
14 siblings, 2 replies; 29+ messages in thread
From: Corvin Köhne @ 2025-08-15 9:00 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Maydell, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block, YannickV
From: YannickV <Y.Vossen@beckhoff.com>
The a9 global timer and arm mp timers rely on the PERIPHCLK as
their clock source. The current implementation does not take
that into account. That causes problems for applications assuming
other frequencies than 1 GHz.
We can now configure frequencies for the a9 global timer and
arm mp timer. By allowing these values to be set according to
the application's needs, we ensure that the timers behave
consistently with the expected system configuration.
The frequency can also be set via the command line, for example
for the a9 global timer:
-global driver=arm.cortex-a9-global-timer,
property=cpu-freq,value=1000000000
Information can be found in the Zynq 7000 SoC Technical
Reference Manual under Timers.
https://docs.amd.com/r/en-US/ug585-zynq-7000-SoC-TRM
Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
---
hw/timer/a9gtimer.c | 8 +++++---
hw/timer/arm_mptimer.c | 15 +++++++++++----
include/hw/timer/a9gtimer.h | 1 +
include/hw/timer/arm_mptimer.h | 2 ++
4 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/hw/timer/a9gtimer.c b/hw/timer/a9gtimer.c
index 9835c35483..a1f5540e75 100644
--- a/hw/timer/a9gtimer.c
+++ b/hw/timer/a9gtimer.c
@@ -63,9 +63,9 @@ static inline int a9_gtimer_get_current_cpu(A9GTimerState *s)
static inline uint64_t a9_gtimer_get_conv(A9GTimerState *s)
{
uint64_t prescale = extract32(s->control, R_CONTROL_PRESCALER_SHIFT,
- R_CONTROL_PRESCALER_LEN);
-
- return (prescale + 1) * 10;
+ R_CONTROL_PRESCALER_LEN) + 1;
+ uint64_t ret = NANOSECONDS_PER_SECOND * prescale * 10;
+ return (uint32_t) (ret / s->cpu_clk_freq_hz);
}
static A9GTimerUpdate a9_gtimer_get_update(A9GTimerState *s)
@@ -374,6 +374,8 @@ static const VMStateDescription vmstate_a9_gtimer = {
};
static const Property a9_gtimer_properties[] = {
+ DEFINE_PROP_UINT64("cpu-freq", A9GTimerState, cpu_clk_freq_hz,
+ NANOSECONDS_PER_SECOND),
DEFINE_PROP_UINT32("num-cpu", A9GTimerState, num_cpu, 0),
};
diff --git a/hw/timer/arm_mptimer.c b/hw/timer/arm_mptimer.c
index 803dad1e8a..a748b6ab1a 100644
--- a/hw/timer/arm_mptimer.c
+++ b/hw/timer/arm_mptimer.c
@@ -59,9 +59,11 @@ static inline void timerblock_update_irq(TimerBlock *tb)
}
/* Return conversion factor from mpcore timer ticks to qemu timer ticks. */
-static inline uint32_t timerblock_scale(uint32_t control)
+static inline uint32_t timerblock_scale(TimerBlock *tb, uint32_t control)
{
- return (((control >> 8) & 0xff) + 1) * 10;
+ uint64_t prescale = (((control >> 8) & 0xff) + 1);
+ uint64_t ret = NANOSECONDS_PER_SECOND * prescale * 10;
+ return (uint32_t) (ret / tb->freq_hz);
}
/* Must be called within a ptimer transaction block */
@@ -155,7 +157,7 @@ static void timerblock_write(void *opaque, hwaddr addr,
ptimer_stop(tb->timer);
}
if ((control & 0xff00) != (value & 0xff00)) {
- ptimer_set_period(tb->timer, timerblock_scale(value));
+ ptimer_set_period(tb->timer, timerblock_scale(tb, value));
}
if (value & 1) {
uint64_t count = ptimer_get_count(tb->timer);
@@ -222,7 +224,8 @@ static void timerblock_reset(TimerBlock *tb)
ptimer_transaction_begin(tb->timer);
ptimer_stop(tb->timer);
ptimer_set_limit(tb->timer, 0, 1);
- ptimer_set_period(tb->timer, timerblock_scale(0));
+ ptimer_set_period(tb->timer,
+ timerblock_scale(tb, tb->control));
ptimer_transaction_commit(tb->timer);
}
}
@@ -269,6 +272,7 @@ static void arm_mptimer_realize(DeviceState *dev, Error **errp)
*/
for (i = 0; i < s->num_cpu; i++) {
TimerBlock *tb = &s->timerblock[i];
+ tb->freq_hz = s->clk_freq_hz;
tb->timer = ptimer_init(timerblock_tick, tb, PTIMER_POLICY);
sysbus_init_irq(sbd, &tb->irq);
memory_region_init_io(&tb->iomem, OBJECT(s), &timerblock_ops, tb,
@@ -283,6 +287,7 @@ static const VMStateDescription vmstate_timerblock = {
.minimum_version_id = 3,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(control, TimerBlock),
+ VMSTATE_UINT64(freq_hz, TimerBlock),
VMSTATE_UINT32(status, TimerBlock),
VMSTATE_PTIMER(timer, TimerBlock),
VMSTATE_END_OF_LIST()
@@ -301,6 +306,8 @@ static const VMStateDescription vmstate_arm_mptimer = {
};
static const Property arm_mptimer_properties[] = {
+ DEFINE_PROP_UINT64("clk-freq", ARMMPTimerState, clk_freq_hz,
+ NANOSECONDS_PER_SECOND),
DEFINE_PROP_UINT32("num-cpu", ARMMPTimerState, num_cpu, 0),
};
diff --git a/include/hw/timer/a9gtimer.h b/include/hw/timer/a9gtimer.h
index 6ae9122e4b..8ce507a793 100644
--- a/include/hw/timer/a9gtimer.h
+++ b/include/hw/timer/a9gtimer.h
@@ -76,6 +76,7 @@ struct A9GTimerState {
MemoryRegion iomem;
/* static props */
+ uint64_t cpu_clk_freq_hz;
uint32_t num_cpu;
QEMUTimer *timer;
diff --git a/include/hw/timer/arm_mptimer.h b/include/hw/timer/arm_mptimer.h
index 65a96e2a0d..8b936cceac 100644
--- a/include/hw/timer/arm_mptimer.h
+++ b/include/hw/timer/arm_mptimer.h
@@ -31,6 +31,7 @@ typedef struct {
uint32_t control;
uint32_t status;
struct ptimer_state *timer;
+ uint64_t freq_hz;
qemu_irq irq;
MemoryRegion iomem;
} TimerBlock;
@@ -43,6 +44,7 @@ struct ARMMPTimerState {
SysBusDevice parent_obj;
/*< public >*/
+ uint64_t clk_freq_hz;
uint32_t num_cpu;
TimerBlock timerblock[ARM_MPTIMER_MAX_CPUS];
MemoryRegion iomem;
--
2.50.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 02/14] hw/timer: Make PERIPHCLK period configurable
2025-08-15 9:00 [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Corvin Köhne
2025-08-15 9:00 ` [PATCH v2 01/14] hw/timer: Make frequency configurable Corvin Köhne
@ 2025-08-15 9:01 ` Corvin Köhne
2025-08-19 16:38 ` Peter Maydell
2025-08-15 9:01 ` [PATCH v2 03/14] hw/dma/zynq-devcfg: Handle bitstream loading via DMA to 0xffffffff Corvin Köhne
` (12 subsequent siblings)
14 siblings, 1 reply; 29+ messages in thread
From: Corvin Köhne @ 2025-08-15 9:01 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Maydell, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block, YannickV
From: YannickV <Y.Vossen@beckhoff.com>
The a9 global timer and arm mp timer rely on the PERIPHCLK
as their clock source. The period of PERIPHCLK (denoted as N)
must be a multiple of the core CLK period, with N being equal
to or greater than two. However, the current implementation
does not take the PERIPHCLK period into account, leading to
unexpected behavior in systems where the application assumes
PERIPHCLK is clocked differently.
The property periphclk-period represents the period N, the CLK
is devided by to get the peripheral clock PERIPHCLK. We can now
configure clock properties for the a9 global timer and arm mp
timer. That ensures timers can behave according to the
applications needs.
The PERIPHCLK period can also be set via the command line, for
example for the a9 global timer:
-global driver=arm.cortex-a9-global-timer,
property=periphclk-period,value=2
Information can be found in the Zynq 7000 Soc Technical
Reference Manual under Timers.
https://docs.amd.com/r/en-US/ug585-zynq-7000-SoC-TRM
Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
---
hw/timer/a9gtimer.c | 19 ++++++++++++++++++-
hw/timer/arm_mptimer.c | 20 +++++++++++++++++++-
include/hw/timer/a9gtimer.h | 1 +
include/hw/timer/arm_mptimer.h | 2 ++
4 files changed, 40 insertions(+), 2 deletions(-)
diff --git a/hw/timer/a9gtimer.c b/hw/timer/a9gtimer.c
index a1f5540e75..83aa75889e 100644
--- a/hw/timer/a9gtimer.c
+++ b/hw/timer/a9gtimer.c
@@ -27,6 +27,7 @@
#include "hw/timer/a9gtimer.h"
#include "migration/vmstate.h"
#include "qapi/error.h"
+#include "qemu/error-report.h"
#include "qemu/timer.h"
#include "qemu/bitops.h"
#include "qemu/log.h"
@@ -62,9 +63,17 @@ static inline int a9_gtimer_get_current_cpu(A9GTimerState *s)
static inline uint64_t a9_gtimer_get_conv(A9GTimerState *s)
{
+ /*
+ * Referring to the ARM-Cortex-A9 MPCore TRM
+ *
+ * The a9 global timer relies on the PERIPHCLK as its clock source.
+ * The PERIPHCLK clock period must be configured as a multiple of the
+ * main clock CLK. The conversion from the qemu clock (1GHz) to a9
+ * gtimer ticks can be calculated like this:
+ */
uint64_t prescale = extract32(s->control, R_CONTROL_PRESCALER_SHIFT,
R_CONTROL_PRESCALER_LEN) + 1;
- uint64_t ret = NANOSECONDS_PER_SECOND * prescale * 10;
+ uint64_t ret = NANOSECONDS_PER_SECOND * prescale * s->periphclk_period;
return (uint32_t) (ret / s->cpu_clk_freq_hz);
}
@@ -312,6 +321,12 @@ static void a9_gtimer_realize(DeviceState *dev, Error **errp)
sysbus_init_mmio(sbd, &s->iomem);
s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, a9_gtimer_update_no_sync, s);
+ if (s->periphclk_period < 2) {
+ error_report("Invalid periphclk-period (%lu), must be >= 2",
+ s->periphclk_period);
+ exit(1);
+ }
+
for (i = 0; i < s->num_cpu; i++) {
A9GTimerPerCPU *gtb = &s->per_cpu[i];
@@ -377,6 +392,8 @@ static const Property a9_gtimer_properties[] = {
DEFINE_PROP_UINT64("cpu-freq", A9GTimerState, cpu_clk_freq_hz,
NANOSECONDS_PER_SECOND),
DEFINE_PROP_UINT32("num-cpu", A9GTimerState, num_cpu, 0),
+ DEFINE_PROP_UINT64("periphclk-period", A9GTimerState,
+ periphclk_period, 10),
};
static void a9_gtimer_class_init(ObjectClass *klass, void *data)
diff --git a/hw/timer/arm_mptimer.c b/hw/timer/arm_mptimer.c
index a748b6ab1a..767413c77a 100644
--- a/hw/timer/arm_mptimer.c
+++ b/hw/timer/arm_mptimer.c
@@ -27,6 +27,7 @@
#include "hw/timer/arm_mptimer.h"
#include "migration/vmstate.h"
#include "qapi/error.h"
+#include "qemu/error-report.h"
#include "qemu/module.h"
#include "hw/core/cpu.h"
@@ -61,8 +62,16 @@ static inline void timerblock_update_irq(TimerBlock *tb)
/* Return conversion factor from mpcore timer ticks to qemu timer ticks. */
static inline uint32_t timerblock_scale(TimerBlock *tb, uint32_t control)
{
+ /*
+ * Referring to the ARM-Cortex-A9 MPCore TRM
+ *
+ * The arm mp timer relies on the PERIPHCLK as its clock source.
+ * The PERIPHCLK clock period must be configured as a multiple of the
+ * main clock CLK. The conversion from the qemu clock (1GHz) to arm mp
+ * timer ticks can be calculated like this:
+ */
uint64_t prescale = (((control >> 8) & 0xff) + 1);
- uint64_t ret = NANOSECONDS_PER_SECOND * prescale * 10;
+ uint64_t ret = NANOSECONDS_PER_SECOND * prescale * tb->periphclk_period;
return (uint32_t) (ret / tb->freq_hz);
}
@@ -273,6 +282,12 @@ static void arm_mptimer_realize(DeviceState *dev, Error **errp)
for (i = 0; i < s->num_cpu; i++) {
TimerBlock *tb = &s->timerblock[i];
tb->freq_hz = s->clk_freq_hz;
+ if (s->periphclk_period < 2) {
+ error_report("Invalid periphclk-period (%lu), must be >= 2",
+ s->periphclk_period);
+ exit(1);
+ }
+ tb->periphclk_period = s->periphclk_period;
tb->timer = ptimer_init(timerblock_tick, tb, PTIMER_POLICY);
sysbus_init_irq(sbd, &tb->irq);
memory_region_init_io(&tb->iomem, OBJECT(s), &timerblock_ops, tb,
@@ -288,6 +303,7 @@ static const VMStateDescription vmstate_timerblock = {
.fields = (const VMStateField[]) {
VMSTATE_UINT32(control, TimerBlock),
VMSTATE_UINT64(freq_hz, TimerBlock),
+ VMSTATE_UINT64(periphclk_period, TimerBlock),
VMSTATE_UINT32(status, TimerBlock),
VMSTATE_PTIMER(timer, TimerBlock),
VMSTATE_END_OF_LIST()
@@ -309,6 +325,8 @@ static const Property arm_mptimer_properties[] = {
DEFINE_PROP_UINT64("clk-freq", ARMMPTimerState, clk_freq_hz,
NANOSECONDS_PER_SECOND),
DEFINE_PROP_UINT32("num-cpu", ARMMPTimerState, num_cpu, 0),
+ DEFINE_PROP_UINT64("periphclk-period", ARMMPTimerState,
+ periphclk_period, 10),
};
static void arm_mptimer_class_init(ObjectClass *klass, void *data)
diff --git a/include/hw/timer/a9gtimer.h b/include/hw/timer/a9gtimer.h
index 8ce507a793..edb51f91e3 100644
--- a/include/hw/timer/a9gtimer.h
+++ b/include/hw/timer/a9gtimer.h
@@ -77,6 +77,7 @@ struct A9GTimerState {
MemoryRegion iomem;
/* static props */
uint64_t cpu_clk_freq_hz;
+ uint64_t periphclk_period;
uint32_t num_cpu;
QEMUTimer *timer;
diff --git a/include/hw/timer/arm_mptimer.h b/include/hw/timer/arm_mptimer.h
index 8b936cceac..2c4cb5c1c3 100644
--- a/include/hw/timer/arm_mptimer.h
+++ b/include/hw/timer/arm_mptimer.h
@@ -32,6 +32,7 @@ typedef struct {
uint32_t status;
struct ptimer_state *timer;
uint64_t freq_hz;
+ uint64_t periphclk_period;
qemu_irq irq;
MemoryRegion iomem;
} TimerBlock;
@@ -45,6 +46,7 @@ struct ARMMPTimerState {
/*< public >*/
uint64_t clk_freq_hz;
+ uint64_t periphclk_period;
uint32_t num_cpu;
TimerBlock timerblock[ARM_MPTIMER_MAX_CPUS];
MemoryRegion iomem;
--
2.50.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 03/14] hw/dma/zynq-devcfg: Handle bitstream loading via DMA to 0xffffffff
2025-08-15 9:00 [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Corvin Köhne
2025-08-15 9:00 ` [PATCH v2 01/14] hw/timer: Make frequency configurable Corvin Köhne
2025-08-15 9:01 ` [PATCH v2 02/14] hw/timer: Make PERIPHCLK period configurable Corvin Köhne
@ 2025-08-15 9:01 ` Corvin Köhne
2025-08-15 9:01 ` [PATCH v2 04/14] hw/arm/zynq-devcfg: Prevent unintended unlock during initialization Corvin Köhne
` (11 subsequent siblings)
14 siblings, 0 replies; 29+ messages in thread
From: Corvin Köhne @ 2025-08-15 9:01 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Maydell, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block, YannickV,
Edgar E. Iglesias
From: YannickV <Y.Vossen@beckhoff.com>
A DMA transfer to destination address `0xffffffff` should trigger a
bitstream load via the PCAP interface. Currently, this case is not
intercepted, causing loaders to enter an infinite loop when polling
the status register.
This commit adds a check for `0xffffffff` as the destination address.
If detected, the relevant status register bits (`DMA_DONE`,
`DMA_P_DONE`, and `PCFG_DONE`) are set to indicate a successful
bitstream load. If the address is different, the DMA transfer proceeds
as usual. A successful load is indicated but nothing is actually
done. Guests relying on FPGA functions are still known to fail.
This feature is required for the integration of the Beckhoff
CX7200 model.
Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
Reviewed-by: Edgar E. Iglesias <edgar.iglesias@amd.com>
---
hw/dma/xlnx-zynq-devcfg.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/hw/dma/xlnx-zynq-devcfg.c b/hw/dma/xlnx-zynq-devcfg.c
index 0fd0d23f57..b838c1c0d0 100644
--- a/hw/dma/xlnx-zynq-devcfg.c
+++ b/hw/dma/xlnx-zynq-devcfg.c
@@ -247,7 +247,14 @@ static uint64_t r_lock_pre_write(RegisterInfo *reg, uint64_t val)
static void r_dma_dst_len_post_write(RegisterInfo *reg, uint64_t val)
{
XlnxZynqDevcfg *s = XLNX_ZYNQ_DEVCFG(reg->opaque);
-
+ if ((s->regs[R_DMA_DST_ADDR]) == 0xffffffff) {
+ DB_PRINT("bitstream loading detected\n");
+ s->regs[R_INT_STS] |= R_INT_STS_DMA_DONE_MASK |
+ R_INT_STS_DMA_P_DONE_MASK |
+ R_INT_STS_PCFG_DONE_MASK;
+ xlnx_zynq_devcfg_update_ixr(s);
+ return;
+ }
s->dma_cmd_fifo[s->dma_cmd_fifo_num] = (XlnxZynqDevcfgDMACmd) {
.src_addr = s->regs[R_DMA_SRC_ADDR] & ~0x3UL,
.dest_addr = s->regs[R_DMA_DST_ADDR] & ~0x3UL,
--
2.50.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 04/14] hw/arm/zynq-devcfg: Prevent unintended unlock during initialization
2025-08-15 9:00 [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Corvin Köhne
` (2 preceding siblings ...)
2025-08-15 9:01 ` [PATCH v2 03/14] hw/dma/zynq-devcfg: Handle bitstream loading via DMA to 0xffffffff Corvin Köhne
@ 2025-08-15 9:01 ` Corvin Köhne
2025-08-24 15:47 ` Edgar E. Iglesias
2025-08-15 9:01 ` [PATCH v2 05/14] hw/dma/zynq: Ensure PCFG_DONE bit remains set to indicate PL is in user mode Corvin Köhne
` (10 subsequent siblings)
14 siblings, 1 reply; 29+ messages in thread
From: Corvin Köhne @ 2025-08-15 9:01 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Maydell, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block, YannickV
From: YannickV <Y.Vossen@beckhoff.com>
During the emulation startup, all registers are reset, which triggers the
`r_unlock_post_write` function with a value of 0. This led to an
unintended memory access disable, making the devcfg unusable.
During startup, the memory space no longer gets locked.
Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
---
hw/dma/xlnx-zynq-devcfg.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/hw/dma/xlnx-zynq-devcfg.c b/hw/dma/xlnx-zynq-devcfg.c
index b838c1c0d0..f28d0015e6 100644
--- a/hw/dma/xlnx-zynq-devcfg.c
+++ b/hw/dma/xlnx-zynq-devcfg.c
@@ -221,7 +221,9 @@ static void r_unlock_post_write(RegisterInfo *reg, uint64_t val)
{
XlnxZynqDevcfg *s = XLNX_ZYNQ_DEVCFG(reg->opaque);
const char *device_prefix = object_get_typename(OBJECT(s));
-
+ if (device_is_in_reset(DEVICE(s))) {
+ return;
+ }
if (val == R_UNLOCK_MAGIC) {
DB_PRINT("successful unlock\n");
s->regs[R_CTRL] |= R_CTRL_PCAP_PR_MASK;
--
2.50.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 05/14] hw/dma/zynq: Ensure PCFG_DONE bit remains set to indicate PL is in user mode
2025-08-15 9:00 [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Corvin Köhne
` (3 preceding siblings ...)
2025-08-15 9:01 ` [PATCH v2 04/14] hw/arm/zynq-devcfg: Prevent unintended unlock during initialization Corvin Köhne
@ 2025-08-15 9:01 ` Corvin Köhne
2025-08-24 16:09 ` Edgar E. Iglesias
2025-08-15 9:01 ` [PATCH v2 06/14] hw/dma/zynq-devcfg: Simulate dummy PL reset Corvin Köhne
` (9 subsequent siblings)
14 siblings, 1 reply; 29+ messages in thread
From: Corvin Köhne @ 2025-08-15 9:01 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Maydell, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block, YannickV
From: YannickV <Y.Vossen@beckhoff.com>
All register bits are clear on write by writing 1s to those bits, however
the register bits will only be cleared if the condition that sets the
interrupt flag is no longer true. Since we can assume that programming
is always done, the `PCFG_DONE` flag is always set to 1, so it will not
never be cleared.
Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
---
hw/dma/xlnx-zynq-devcfg.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/hw/dma/xlnx-zynq-devcfg.c b/hw/dma/xlnx-zynq-devcfg.c
index f28d0015e6..60ea351494 100644
--- a/hw/dma/xlnx-zynq-devcfg.c
+++ b/hw/dma/xlnx-zynq-devcfg.c
@@ -188,6 +188,8 @@ static void r_ixr_post_write(RegisterInfo *reg, uint64_t val)
{
XlnxZynqDevcfg *s = XLNX_ZYNQ_DEVCFG(reg->opaque);
+ s->regs[R_INT_STS] |= R_INT_STS_PCFG_DONE_MASK;
+
xlnx_zynq_devcfg_update_ixr(s);
}
--
2.50.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 06/14] hw/dma/zynq-devcfg: Simulate dummy PL reset
2025-08-15 9:00 [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Corvin Köhne
` (4 preceding siblings ...)
2025-08-15 9:01 ` [PATCH v2 05/14] hw/dma/zynq: Ensure PCFG_DONE bit remains set to indicate PL is in user mode Corvin Köhne
@ 2025-08-15 9:01 ` Corvin Köhne
2025-08-24 15:53 ` Edgar E. Iglesias
2025-08-15 9:01 ` [PATCH v2 07/14] hw/dma/zynq-devcfg: Indicate power-up status of PL Corvin Köhne
` (8 subsequent siblings)
14 siblings, 1 reply; 29+ messages in thread
From: Corvin Köhne @ 2025-08-15 9:01 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Maydell, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block, YannickV
From: YannickV <Y.Vossen@beckhoff.com>
Setting PCFG_PROG_B should reset the PL. After a reset PCFG_INIT
should indicate that the reset is finished successfully.
In order to add a MMIO-Device as part of the PL in the Zynq, the
reset logic must succeed. The PCFG_INIT flag is now set when the
PL reset is triggered by PCFG_PROG_B. Indicating the reset was
successful.
Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
---
hw/dma/xlnx-zynq-devcfg.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/hw/dma/xlnx-zynq-devcfg.c b/hw/dma/xlnx-zynq-devcfg.c
index 60ea351494..c699df6ad4 100644
--- a/hw/dma/xlnx-zynq-devcfg.c
+++ b/hw/dma/xlnx-zynq-devcfg.c
@@ -49,6 +49,7 @@
REG32(CTRL, 0x00)
FIELD(CTRL, FORCE_RST, 31, 1) /* Not supported, wr ignored */
+ FIELD(CTRL, PCFG_PROG_B, 30, 1)
FIELD(CTRL, PCAP_PR, 27, 1) /* Forced to 0 on bad unlock */
FIELD(CTRL, PCAP_MODE, 26, 1)
FIELD(CTRL, MULTIBOOT_EN, 24, 1)
@@ -116,6 +117,7 @@ REG32(STATUS, 0x14)
FIELD(STATUS, PSS_GTS_USR_B, 11, 1)
FIELD(STATUS, PSS_FST_CFG_B, 10, 1)
FIELD(STATUS, PSS_CFG_RESET_B, 5, 1)
+ FIELD(STATUS, PCFG_INIT, 4, 1)
REG32(DMA_SRC_ADDR, 0x18)
REG32(DMA_DST_ADDR, 0x1C)
@@ -204,6 +206,13 @@ static uint64_t r_ctrl_pre_write(RegisterInfo *reg, uint64_t val)
val |= lock_ctrl_map[i] & s->regs[R_CTRL];
}
}
+
+ if (FIELD_EX32(val, CTRL, PCFG_PROG_B)) {
+ s->regs[R_STATUS] |= R_STATUS_PCFG_INIT_MASK;
+ } else {
+ s->regs[R_STATUS] &= ~R_STATUS_PCFG_INIT_MASK;
+ }
+
return val;
}
--
2.50.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 07/14] hw/dma/zynq-devcfg: Indicate power-up status of PL
2025-08-15 9:00 [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Corvin Köhne
` (5 preceding siblings ...)
2025-08-15 9:01 ` [PATCH v2 06/14] hw/dma/zynq-devcfg: Simulate dummy PL reset Corvin Köhne
@ 2025-08-15 9:01 ` Corvin Köhne
2025-08-24 16:11 ` Edgar E. Iglesias
2025-08-15 9:01 ` [PATCH v2 08/14] hw/dma/zynq-devcfg: Fix register memory Corvin Köhne
` (7 subsequent siblings)
14 siblings, 1 reply; 29+ messages in thread
From: Corvin Köhne @ 2025-08-15 9:01 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Maydell, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block, YannickV
From: YannickV <Y.Vossen@beckhoff.com>
It is assumed, that the programmable logic (PL) is always powered
during emulation. Therefor the PCFG_POR_B bit in the MCTRL register
is set.
Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
---
hw/dma/xlnx-zynq-devcfg.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/hw/dma/xlnx-zynq-devcfg.c b/hw/dma/xlnx-zynq-devcfg.c
index c699df6ad4..064955a0f8 100644
--- a/hw/dma/xlnx-zynq-devcfg.c
+++ b/hw/dma/xlnx-zynq-devcfg.c
@@ -333,7 +333,8 @@ static const RegisterAccessInfo xlnx_zynq_devcfg_regs_info[] = {
/* Silicon 3.0 for version field, the mysterious reserved bit 23
* and QEMU platform identifier.
*/
- .reset = 0x2 << R_MCTRL_PS_VERSION_SHIFT | 1 << 23 | R_MCTRL_QEMU_MASK,
+ .reset = 0x2 << R_MCTRL_PS_VERSION_SHIFT | 1 << 23 |
+ R_MCTRL_PCFG_POR_B_MASK | R_MCTRL_QEMU_MASK,
.ro = ~R_MCTRL_INT_PCAP_LPBK_MASK,
.rsvd = 0x00f00303,
},
--
2.50.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 08/14] hw/dma/zynq-devcfg: Fix register memory
2025-08-15 9:00 [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Corvin Köhne
` (6 preceding siblings ...)
2025-08-15 9:01 ` [PATCH v2 07/14] hw/dma/zynq-devcfg: Indicate power-up status of PL Corvin Köhne
@ 2025-08-15 9:01 ` Corvin Köhne
2025-08-15 9:01 ` [PATCH v2 09/14] hw/misc: Add dummy ZYNQ DDR controller Corvin Köhne
` (6 subsequent siblings)
14 siblings, 0 replies; 29+ messages in thread
From: Corvin Köhne @ 2025-08-15 9:01 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Maydell, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block, YannickV,
Edgar E. Iglesias
From: YannickV <Y.Vossen@beckhoff.com>
Registers are always 32 bit aligned. R_MAX is not the maximum
register address, it is the maximum register number. The memory
size can be determined by 4 * R_MAX.
Currently every register with an offset bigger than 0x40 will be
ignored, because the memory size is set wrong. This effects the
MCTRL register and makes it useless. This commit restores the
correct behaviour.
Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
Reviewed-by: Edgar E. Iglesias <edgar.iglesias@amd.com>
---
hw/dma/xlnx-zynq-devcfg.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hw/dma/xlnx-zynq-devcfg.c b/hw/dma/xlnx-zynq-devcfg.c
index 064955a0f8..83570ccfaa 100644
--- a/hw/dma/xlnx-zynq-devcfg.c
+++ b/hw/dma/xlnx-zynq-devcfg.c
@@ -393,7 +393,7 @@ static void xlnx_zynq_devcfg_init(Object *obj)
s->regs_info, s->regs,
&xlnx_zynq_devcfg_reg_ops,
XLNX_ZYNQ_DEVCFG_ERR_DEBUG,
- XLNX_ZYNQ_DEVCFG_R_MAX);
+ XLNX_ZYNQ_DEVCFG_R_MAX * 4);
memory_region_add_subregion(&s->iomem,
A_CTRL,
®_array->mem);
--
2.50.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 09/14] hw/misc: Add dummy ZYNQ DDR controller
2025-08-15 9:00 [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Corvin Köhne
` (7 preceding siblings ...)
2025-08-15 9:01 ` [PATCH v2 08/14] hw/dma/zynq-devcfg: Fix register memory Corvin Köhne
@ 2025-08-15 9:01 ` Corvin Köhne
2025-08-19 15:43 ` Peter Maydell
2025-08-15 9:01 ` [PATCH v2 10/14] hw/misc/zynq_slcr: Add logic for DCI configuration Corvin Köhne
` (5 subsequent siblings)
14 siblings, 1 reply; 29+ messages in thread
From: Corvin Köhne @ 2025-08-15 9:01 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Maydell, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block, YannickV
From: YannickV <Y.Vossen@beckhoff.com>
A dummy DDR controller for ZYNQ has been added. While all registers are present,
not all are functional. Read and write access is validated, and the user mode
can be set. This provides a basic DDR controller initialization, preventing
system hangs due to endless polling or similar issues.
Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
---
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/misc/xlnx-zynq-ddrc.c | 393 +++++++++++++++++++++++++++++++
include/hw/misc/xlnx-zynq-ddrc.h | 140 +++++++++++
4 files changed, 537 insertions(+)
create mode 100644 hw/misc/xlnx-zynq-ddrc.c
create mode 100644 include/hw/misc/xlnx-zynq-ddrc.h
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 8f9ce2f68c..99548e146f 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -220,4 +220,7 @@ config IOSB
config XLNX_VERSAL_TRNG
bool
+config XLNX_ZYNQ_DDRC
+ bool
+
source macio/Kconfig
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 55f493521b..6ee7b6c71d 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -89,6 +89,7 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
))
system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
+system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('xlnx-zynq-ddrc.c'))
system_ss.add(when: 'CONFIG_XLNX_ZYNQMP_ARM', if_true: files('xlnx-zynqmp-crf.c'))
system_ss.add(when: 'CONFIG_XLNX_ZYNQMP_ARM', if_true: files('xlnx-zynqmp-apu-ctrl.c'))
system_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files(
diff --git a/hw/misc/xlnx-zynq-ddrc.c b/hw/misc/xlnx-zynq-ddrc.c
new file mode 100644
index 0000000000..8151a0e3ee
--- /dev/null
+++ b/hw/misc/xlnx-zynq-ddrc.c
@@ -0,0 +1,393 @@
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "hw/register.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "hw/registerfields.h"
+#include "system/block-backend.h"
+#include "exec/address-spaces.h"
+#include "exec/memory.h"
+#include "system/dma.h"
+#include "hw/misc/xlnx-zynq-ddrc.h"
+
+#ifndef DDRCTRL_ERR_DEBUG
+#define DDRCTRL_ERR_DEBUG 0
+#endif
+
+static void zynq_ddrctrl_post_write(RegisterInfo *reg, uint64_t val)
+{
+ DDRCTRLState *s = DDRCTRL(reg->opaque);
+ if (reg->access->addr == A_DDRC_CTRL) {
+ if (val & 0x1) {
+ s->reg[R_MODE_STS_REG] |=
+ (R_MODE_STS_REG_DDR_REG_OPERATING_MODE_MASK & 0x1);
+ } else {
+ s->reg[R_MODE_STS_REG] &=
+ ~R_MODE_STS_REG_DDR_REG_OPERATING_MODE_MASK;
+ }
+ }
+}
+
+static const RegisterAccessInfo xlnx_zynq_ddrc_regs_info[] = {
+ /* 0x00 - 0x3C: Basic DDRC control and config */
+ { .name = "DDRC_CTRL",
+ .addr = A_DDRC_CTRL,
+ .reset = 0x00000200,
+ .post_write = zynq_ddrctrl_post_write },
+ { .name = "TWO_RANK_CFG",
+ .addr = A_TWO_RANK_CFG,
+ .reset = 0x000C1076 },
+ { .name = "HPR_REG",
+ .addr = A_HPR_REG,
+ .reset = 0x03C0780F },
+ { .name = "LPR_REG",
+ .addr = A_LPR_REG,
+ .reset = 0x03C0780F },
+ { .name = "WR_REG",
+ .addr = A_WR_REG,
+ .reset = 0x0007F80F },
+ { .name = "DRAM_PARAM_REG0",
+ .addr = A_DRAM_PARAM_REG0,
+ .reset = 0x00041016 },
+ { .name = "DRAM_PARAM_REG1",
+ .addr = A_DRAM_PARAM_REG1,
+ .reset = 0x351B48D9 },
+ { .name = "DRAM_PARAM_REG2",
+ .addr = A_DRAM_PARAM_REG2,
+ .reset = 0x83015904 },
+ { .name = "DRAM_PARAM_REG3",
+ .addr = A_DRAM_PARAM_REG3,
+ .reset = 0x250882D0 },
+ { .name = "DRAM_PARAM_REG4",
+ .addr = A_DRAM_PARAM_REG4,
+ .reset = 0x0000003C },
+ { .name = "DRAM_INIT_PARAM",
+ .addr = A_DRAM_INIT_PARAM,
+ .reset = 0x00002007 },
+ { .name = "DRAM_EMR_REG",
+ .addr = A_DRAM_EMR_REG,
+ .reset = 0x00000008 },
+ { .name = "DRAM_EMR_MR_REG",
+ .addr = A_DRAM_EMR_MR_REG,
+ .reset = 0x00000940 },
+ { .name = "DRAM_BURST8_RDWR",
+ .addr = A_DRAM_BURST8_RDWR,
+ .reset = 0x00020034 },
+ { .name = "DRAM_DISABLE_DQ",
+ .addr = A_DRAM_DISABLE_DQ },
+ { .name = "DRAM_ADDR_MAP_BANK",
+ .addr = A_DRAM_ADDR_MAP_BANK,
+ .reset = 0x00000F77 },
+ { .name = "DRAM_ADDR_MAP_COL",
+ .addr = A_DRAM_ADDR_MAP_COL,
+ .reset = 0xFFF00000 },
+ { .name = "DRAM_ADDR_MAP_ROW",
+ .addr = A_DRAM_ADDR_MAP_ROW,
+ .reset = 0x0FF55555 },
+ { .name = "DRAM_ODT_REG",
+ .addr = A_DRAM_ODT_REG,
+ .reset = 0x00000249 },
+
+ /* 0x4C - 0x5C: PHY and DLL */
+ { .name = "PHY_DBG_REG",
+ .addr = A_PHY_DBG_REG },
+ { .name = "PHY_CMD_TIMEOUT_RDDATA_CPT",
+ .addr = A_PHY_CMD_TIMEOUT_RDDATA_CPT,
+ .reset = 0x00010200 },
+ { .name = "MODE_STS_REG",
+ .addr = A_MODE_STS_REG },
+ { .name = "DLL_CALIB",
+ .addr = A_DLL_CALIB,
+ .reset = 0x00000101 },
+ { .name = "ODT_DELAY_HOLD",
+ .addr = A_ODT_DELAY_HOLD,
+ .reset = 0x00000023 },
+
+ /* 0x60 - 0x7C: Control registers */
+ { .name = "CTRL_REG1",
+ .addr = A_CTRL_REG1,
+ .reset = 0x0000003E },
+ { .name = "CTRL_REG2",
+ .addr = A_CTRL_REG2,
+ .reset = 0x00020000 },
+ { .name = "CTRL_REG3",
+ .addr = A_CTRL_REG3,
+ .reset = 0x00284027 },
+ { .name = "CTRL_REG4",
+ .addr = A_CTRL_REG4,
+ .reset = 0x00001610 },
+ { .name = "CTRL_REG5",
+ .addr = A_CTRL_REG5,
+ .reset = 0x00455111 },
+ { .name = "CTRL_REG6",
+ .addr = A_CTRL_REG6,
+ .reset = 0x00032222 },
+
+ /* 0xA0 - 0xB4: Refresh, ZQ, powerdown, misc */
+ { .name = "CHE_REFRESH_TIMER0",
+ .addr = A_CHE_REFRESH_TIMER0,
+ .reset = 0x00008000 },
+ { .name = "CHE_T_ZQ",
+ .addr = A_CHE_T_ZQ,
+ .reset = 0x10300802 },
+ { .name = "CHE_T_ZQ_SHORT_INTERVAL_REG",
+ .addr = A_CHE_T_ZQ_SHORT_INTERVAL_REG,
+ .reset = 0x0020003A },
+ { .name = "DEEP_PWRDWN_REG",
+ .addr = A_DEEP_PWRDWN_REG },
+ { .name = "REG_2C",
+ .addr = A_REG_2C },
+ { .name = "REG_2D",
+ .addr = A_REG_2D,
+ .reset = 0x00000200 },
+
+ /* 0xB8 - 0xF8: ECC, DFI, etc. */
+ { .name = "DFI_TIMING",
+ .addr = A_DFI_TIMING,
+ .reset = 0x00200067 },
+ { .name = "CHE_ECC_CONTROL_REG_OFFSET",
+ .addr = A_CHE_ECC_CONTROL_REG_OFFSET },
+ { .name = "CHE_CORR_ECC_LOG_REG_OFFSET",
+ .addr = A_CHE_CORR_ECC_LOG_REG_OFFSET },
+ { .name = "CHE_CORR_ECC_ADDR_REG_OFFSET",
+ .addr = A_CHE_CORR_ECC_ADDR_REG_OFFSET },
+ { .name = "CHE_CORR_ECC_DATA_31_0_REG_OFFSET",
+ .addr = A_CHE_CORR_ECC_DATA_31_0_REG_OFFSET },
+ { .name = "CHE_CORR_ECC_DATA_63_32_REG_OFFSET",
+ .addr = A_CHE_CORR_ECC_DATA_63_32_REG_OFFSET },
+ { .name = "CHE_CORR_ECC_DATA_71_64_REG_OFFSET",
+ .addr = A_CHE_CORR_ECC_DATA_71_64_REG_OFFSET },
+ { .name = "CHE_UNCORR_ECC_LOG_REG_OFFSET",
+ .addr = A_CHE_UNCORR_ECC_LOG_REG_OFFSET },
+ { .name = "CHE_UNCORR_ECC_ADDR_REG_OFFSET",
+ .addr = A_CHE_UNCORR_ECC_ADDR_REG_OFFSET },
+ { .name = "CHE_UNCORR_ECC_DATA_31_0_REG_OFFSET",
+ .addr = A_CHE_UNCORR_ECC_DATA_31_0_REG_OFFSET },
+ { .name = "CHE_UNCORR_ECC_DATA_63_32_REG_OFFSET",
+ .addr = A_CHE_UNCORR_ECC_DATA_63_32_REG_OFFSET },
+ { .name = "CHE_UNCORR_ECC_DATA_71_64_REG_OFFSET",
+ .addr = A_CHE_UNCORR_ECC_DATA_71_64_REG_OFFSET },
+ { .name = "CHE_ECC_STATS_REG_OFFSET",
+ .addr = A_CHE_ECC_STATS_REG_OFFSET },
+ { .name = "ECC_SCRUB",
+ .addr = A_ECC_SCRUB,
+ .reset = 0x00000008 },
+ { .name = "CHE_ECC_CORR_BIT_MASK_31_0_REG_OFFSET",
+ .addr = A_CHE_ECC_CORR_BIT_MASK_31_0_REG_OFFSET },
+ { .name = "CHE_ECC_CORR_BIT_MASK_63_32_REG_OFFSET",
+ .addr = A_CHE_ECC_CORR_BIT_MASK_63_32_REG_OFFSET },
+
+ /* 0x114 - 0x174: PHY config, ratios, DQS, WE */
+ { .name = "PHY_RCVER_ENABLE",
+ .addr = A_PHY_RCVER_ENABLE },
+ { .name = "PHY_CONFIG0",
+ .addr = A_PHY_CONFIG0,
+ .reset = 0x40000001 },
+ { .name = "PHY_CONFIG1",
+ .addr = A_PHY_CONFIG1,
+ .reset = 0x40000001 },
+ { .name = "PHY_CONFIG2",
+ .addr = A_PHY_CONFIG2,
+ .reset = 0x40000001 },
+ { .name = "PHY_CONFIG3",
+ .addr = A_PHY_CONFIG3,
+ .reset = 0x40000001 },
+ { .name = "PHY_INIT_RATIO0",
+ .addr = A_PHY_INIT_RATIO0 },
+ { .name = "PHY_INIT_RATIO1",
+ .addr = A_PHY_INIT_RATIO1 },
+ { .name = "PHY_INIT_RATIO2",
+ .addr = A_PHY_INIT_RATIO2 },
+ { .name = "PHY_INIT_RATIO3",
+ .addr = A_PHY_INIT_RATIO3 },
+ { .name = "PHY_RD_DQS_CFG0",
+ .addr = A_PHY_RD_DQS_CFG0,
+ .reset = 0x00000040 },
+ { .name = "PHY_RD_DQS_CFG1",
+ .addr = A_PHY_RD_DQS_CFG1,
+ .reset = 0x00000040 },
+ { .name = "PHY_RD_DQS_CFG2",
+ .addr = A_PHY_RD_DQS_CFG2,
+ .reset = 0x00000040 },
+ { .name = "PHY_RD_DQS_CFG3",
+ .addr = A_PHY_RD_DQS_CFG3,
+ .reset = 0x00000040 },
+ { .name = "PHY_WR_DQS_CFG0",
+ .addr = A_PHY_WR_DQS_CFG0 },
+ { .name = "PHY_WR_DQS_CFG1",
+ .addr = A_PHY_WR_DQS_CFG1 },
+ { .name = "PHY_WR_DQS_CFG2",
+ .addr = A_PHY_WR_DQS_CFG2 },
+ { .name = "PHY_WR_DQS_CFG3",
+ .addr = A_PHY_WR_DQS_CFG3 },
+ { .name = "PHY_WE_CFG0",
+ .addr = A_PHY_WE_CFG0,
+ .reset = 0x00000040 },
+ { .name = "PHY_WE_CFG1",
+ .addr = A_PHY_WE_CFG1,
+ .reset = 0x00000040 },
+ { .name = "PHY_WE_CFG2",
+ .addr = A_PHY_WE_CFG2,
+ .reset = 0x00000040 },
+ { .name = "PHY_WE_CFG3",
+ .addr = A_PHY_WE_CFG3,
+ .reset = 0x00000040 },
+
+ /* 0x17C - 0x194: Write data slaves, misc */
+ { .name = "WR_DATA_SLV0",
+ .addr = A_WR_DATA_SLV0,
+ .reset = 0x00000080 },
+ { .name = "WR_DATA_SLV1",
+ .addr = A_WR_DATA_SLV1,
+ .reset = 0x00000080 },
+ { .name = "WR_DATA_SLV2",
+ .addr = A_WR_DATA_SLV2,
+ .reset = 0x00000080 },
+ { .name = "WR_DATA_SLV3",
+ .addr = A_WR_DATA_SLV3,
+ .reset = 0x00000080 },
+ { .name = "REG_64",
+ .addr = A_REG_64,
+ .reset = 0x10020000 },
+ { .name = "REG_65",
+ .addr = A_REG_65 },
+
+ /* 0x1A4 - 0x1C4: Misc registers */
+ { .name = "REG69_6A0",
+ .addr = A_REG69_6A0 },
+ { .name = "REG69_6A1",
+ .addr = A_REG69_6A1 },
+ { .name = "REG6C_6D2",
+ .addr = A_REG6C_6D2 },
+ { .name = "REG6C_6D3",
+ .addr = A_REG6C_6D3 },
+ { .name = "REG6E_710",
+ .addr = A_REG6E_710 },
+ { .name = "REG6E_711",
+ .addr = A_REG6E_711 },
+ { .name = "REG6E_712",
+ .addr = A_REG6E_712 },
+ { .name = "REG6E_713",
+ .addr = A_REG6E_713 },
+
+ /* 0x1CC - 0x1E8: DLL, PHY status */
+ { .name = "PHY_DLL_STS0",
+ .addr = A_PHY_DLL_STS0 },
+ { .name = "PHY_DLL_STS1",
+ .addr = A_PHY_DLL_STS1 },
+ { .name = "PHY_DLL_STS2",
+ .addr = A_PHY_DLL_STS2 },
+ { .name = "PHY_DLL_STS3",
+ .addr = A_PHY_DLL_STS3 },
+ { .name = "DLL_LOCK_STS",
+ .addr = A_DLL_LOCK_STS },
+ { .name = "PHY_CTRL_STS",
+ .addr = A_PHY_CTRL_STS },
+ { .name = "PHY_CTRL_STS_REG2",
+ .addr = A_PHY_CTRL_STS_REG2 },
+
+ /* 0x200 - 0x2B4: AXI, LPDDR, misc */
+ { .name = "AXI_ID",
+ .addr = A_AXI_ID },
+ { .name = "PAGE_MASK",
+ .addr = A_PAGE_MASK },
+ { .name = "AXI_PRIORITY_WR_PORT0",
+ .addr = A_AXI_PRIORITY_WR_PORT0,
+ .reset = 0x000803FF },
+ { .name = "AXI_PRIORITY_WR_PORT1",
+ .addr = A_AXI_PRIORITY_WR_PORT1,
+ .reset = 0x000803FF },
+ { .name = "AXI_PRIORITY_WR_PORT2",
+ .addr = A_AXI_PRIORITY_WR_PORT2,
+ .reset = 0x000803FF },
+ { .name = "AXI_PRIORITY_WR_PORT3",
+ .addr = A_AXI_PRIORITY_WR_PORT3,
+ .reset = 0x000803FF },
+ { .name = "AXI_PRIORITY_RD_PORT0",
+ .addr = A_AXI_PRIORITY_RD_PORT0,
+ .reset = 0x000003FF },
+ { .name = "AXI_PRIORITY_RD_PORT1",
+ .addr = A_AXI_PRIORITY_RD_PORT1,
+ .reset = 0x000003FF },
+ { .name = "AXI_PRIORITY_RD_PORT2",
+ .addr = A_AXI_PRIORITY_RD_PORT2,
+ .reset = 0x000003FF },
+ { .name = "AXI_PRIORITY_RD_PORT3",
+ .addr = A_AXI_PRIORITY_RD_PORT3,
+ .reset = 0x000003FF },
+ { .name = "EXCL_ACCESS_CFG0",
+ .addr = A_EXCL_ACCESS_CFG0 },
+ { .name = "EXCL_ACCESS_CFG1",
+ .addr = A_EXCL_ACCESS_CFG1 },
+ { .name = "EXCL_ACCESS_CFG2",
+ .addr = A_EXCL_ACCESS_CFG2 },
+ { .name = "EXCL_ACCESS_CFG3",
+ .addr = A_EXCL_ACCESS_CFG3 },
+ { .name = "MODE_REG_READ",
+ .addr = A_MODE_REG_READ },
+ { .name = "LPDDR_CTRL0",
+ .addr = A_LPDDR_CTRL0 },
+ { .name = "LPDDR_CTRL1",
+ .addr = A_LPDDR_CTRL1 },
+ { .name = "LPDDR_CTRL2",
+ .addr = A_LPDDR_CTRL2,
+ .reset = 0x003C0015 },
+ { .name = "LPDDR_CTRL3",
+ .addr = A_LPDDR_CTRL3,
+ .reset = 0x00000601 },
+};
+
+static void zynq_ddrctrl_reset(DeviceState *dev)
+{
+ DDRCTRLState *s = DDRCTRL(dev);
+ int i;
+
+ for (i = 0; i < ZYNQ_DDRCTRL_NUM_REG; ++i) {
+ register_reset(&s->regs_info[i]);
+ }
+}
+
+static const MemoryRegionOps ddrctrl_ops = {
+ .read = register_read_memory,
+ .write = register_write_memory,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void zynq_ddrctrl_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ DDRCTRLState *s = DDRCTRL(obj);
+
+ s->reg_array =
+ register_init_block32(DEVICE(obj), xlnx_zynq_ddrc_regs_info,
+ ARRAY_SIZE(xlnx_zynq_ddrc_regs_info),
+ s->regs_info, s->reg,
+ &ddrctrl_ops,
+ DDRCTRL_ERR_DEBUG,
+ ZYNQ_DDRCTRL_MMIO_SIZE);
+
+ sysbus_init_mmio(sbd, &s->reg_array->mem);
+}
+
+static void zynq_ddrctrl_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, zynq_ddrctrl_reset);
+}
+
+static const TypeInfo ddrctrl_info = {
+ .name = TYPE_DDRCTRL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(DDRCTRLState),
+ .class_init = zynq_ddrctrl_class_init,
+ .instance_init = zynq_ddrctrl_init,
+};
+
+static void ddrctrl_register_types(void)
+{
+ type_register_static(&ddrctrl_info);
+}
+
+type_init(ddrctrl_register_types)
diff --git a/include/hw/misc/xlnx-zynq-ddrc.h b/include/hw/misc/xlnx-zynq-ddrc.h
new file mode 100644
index 0000000000..7b9b4d551e
--- /dev/null
+++ b/include/hw/misc/xlnx-zynq-ddrc.h
@@ -0,0 +1,140 @@
+#ifndef XLNX_ZYNQ_DDRC_H
+#define XLNX_ZYNQ_DDRC_H
+
+#include "hw/sysbus.h"
+#include "hw/register.h"
+
+#define TYPE_DDRCTRL "zynq.ddr-ctlr"
+#define DDRCTRL(obj) \
+ OBJECT_CHECK(DDRCTRLState, (obj), TYPE_DDRCTRL)
+
+REG32(DDRC_CTRL, 0x00)
+REG32(TWO_RANK_CFG, 0x04)
+REG32(HPR_REG, 0x08)
+REG32(LPR_REG, 0x0C)
+REG32(WR_REG, 0x10)
+REG32(DRAM_PARAM_REG0, 0x14)
+REG32(DRAM_PARAM_REG1, 0x18)
+REG32(DRAM_PARAM_REG2, 0x1C)
+REG32(DRAM_PARAM_REG3, 0x20)
+REG32(DRAM_PARAM_REG4, 0x24)
+REG32(DRAM_INIT_PARAM, 0x28)
+REG32(DRAM_EMR_REG, 0x2C)
+REG32(DRAM_EMR_MR_REG, 0x30)
+REG32(DRAM_BURST8_RDWR, 0x34)
+REG32(DRAM_DISABLE_DQ, 0x38)
+REG32(DRAM_ADDR_MAP_BANK, 0x3C)
+REG32(DRAM_ADDR_MAP_COL, 0x40)
+REG32(DRAM_ADDR_MAP_ROW, 0x44)
+REG32(DRAM_ODT_REG, 0x48)
+REG32(PHY_DBG_REG, 0x4C)
+REG32(PHY_CMD_TIMEOUT_RDDATA_CPT, 0x50)
+REG32(MODE_STS_REG, 0x54)
+ FIELD(MODE_STS_REG, DDR_REG_DBG_STALL, 3, 3)
+ FIELD(MODE_STS_REG, DDR_REG_OPERATING_MODE, 0, 2)
+REG32(DLL_CALIB, 0x58)
+REG32(ODT_DELAY_HOLD, 0x5C)
+REG32(CTRL_REG1, 0x60)
+REG32(CTRL_REG2, 0x64)
+REG32(CTRL_REG3, 0x68)
+REG32(CTRL_REG4, 0x6C)
+REG32(CTRL_REG5, 0x78)
+REG32(CTRL_REG6, 0x7C)
+REG32(CHE_REFRESH_TIMER0, 0xA0)
+REG32(CHE_T_ZQ, 0xA4)
+REG32(CHE_T_ZQ_SHORT_INTERVAL_REG, 0xA8)
+REG32(DEEP_PWRDWN_REG, 0xAC)
+REG32(REG_2C, 0xB0)
+REG32(REG_2D, 0xB4)
+REG32(DFI_TIMING, 0xB8)
+REG32(CHE_ECC_CONTROL_REG_OFFSET, 0xC4)
+REG32(CHE_CORR_ECC_LOG_REG_OFFSET, 0xC8)
+REG32(CHE_CORR_ECC_ADDR_REG_OFFSET, 0xCC)
+REG32(CHE_CORR_ECC_DATA_31_0_REG_OFFSET, 0xD0)
+REG32(CHE_CORR_ECC_DATA_63_32_REG_OFFSET, 0xD4)
+REG32(CHE_CORR_ECC_DATA_71_64_REG_OFFSET, 0xD8)
+REG32(CHE_UNCORR_ECC_LOG_REG_OFFSET, 0xDC)
+REG32(CHE_UNCORR_ECC_ADDR_REG_OFFSET, 0xE0)
+REG32(CHE_UNCORR_ECC_DATA_31_0_REG_OFFSET, 0xE4)
+REG32(CHE_UNCORR_ECC_DATA_63_32_REG_OFFSET, 0xE8)
+REG32(CHE_UNCORR_ECC_DATA_71_64_REG_OFFSET, 0xEC)
+REG32(CHE_ECC_STATS_REG_OFFSET, 0xF0)
+REG32(ECC_SCRUB, 0xF4)
+REG32(CHE_ECC_CORR_BIT_MASK_31_0_REG_OFFSET, 0xF8)
+REG32(CHE_ECC_CORR_BIT_MASK_63_32_REG_OFFSET, 0xFC)
+REG32(PHY_RCVER_ENABLE, 0x114)
+REG32(PHY_CONFIG0, 0x118)
+REG32(PHY_CONFIG1, 0x11C)
+REG32(PHY_CONFIG2, 0x120)
+REG32(PHY_CONFIG3, 0x124)
+REG32(PHY_INIT_RATIO0, 0x12C)
+REG32(PHY_INIT_RATIO1, 0x130)
+REG32(PHY_INIT_RATIO2, 0x134)
+REG32(PHY_INIT_RATIO3, 0x138)
+REG32(PHY_RD_DQS_CFG0, 0x140)
+REG32(PHY_RD_DQS_CFG1, 0x144)
+REG32(PHY_RD_DQS_CFG2, 0x148)
+REG32(PHY_RD_DQS_CFG3, 0x14C)
+REG32(PHY_WR_DQS_CFG0, 0x154)
+REG32(PHY_WR_DQS_CFG1, 0x158)
+REG32(PHY_WR_DQS_CFG2, 0x15C)
+REG32(PHY_WR_DQS_CFG3, 0x160)
+REG32(PHY_WE_CFG0, 0x168)
+REG32(PHY_WE_CFG1, 0x16C)
+REG32(PHY_WE_CFG2, 0x170)
+REG32(PHY_WE_CFG3, 0x174)
+REG32(WR_DATA_SLV0, 0x17C)
+REG32(WR_DATA_SLV1, 0x180)
+REG32(WR_DATA_SLV2, 0x184)
+REG32(WR_DATA_SLV3, 0x188)
+REG32(REG_64, 0x190)
+REG32(REG_65, 0x194)
+REG32(REG69_6A0, 0x1A4)
+REG32(REG69_6A1, 0x1A8)
+REG32(REG6C_6D2, 0x1B0)
+REG32(REG6C_6D3, 0x1B4)
+REG32(REG6E_710, 0x1B8)
+REG32(REG6E_711, 0x1BC)
+REG32(REG6E_712, 0x1C0)
+REG32(REG6E_713, 0x1C4)
+REG32(PHY_DLL_STS0, 0x1CC)
+REG32(PHY_DLL_STS1, 0x1D0)
+REG32(PHY_DLL_STS2, 0x1D4)
+REG32(PHY_DLL_STS3, 0x1D8)
+REG32(DLL_LOCK_STS, 0x1E0)
+REG32(PHY_CTRL_STS, 0x1E4)
+REG32(PHY_CTRL_STS_REG2, 0x1E8)
+REG32(AXI_ID, 0x200)
+REG32(PAGE_MASK, 0x204)
+REG32(AXI_PRIORITY_WR_PORT0, 0x208)
+REG32(AXI_PRIORITY_WR_PORT1, 0x20C)
+REG32(AXI_PRIORITY_WR_PORT2, 0x210)
+REG32(AXI_PRIORITY_WR_PORT3, 0x214)
+REG32(AXI_PRIORITY_RD_PORT0, 0x218)
+REG32(AXI_PRIORITY_RD_PORT1, 0x21C)
+REG32(AXI_PRIORITY_RD_PORT2, 0x220)
+REG32(AXI_PRIORITY_RD_PORT3, 0x224)
+REG32(EXCL_ACCESS_CFG0, 0x294)
+REG32(EXCL_ACCESS_CFG1, 0x298)
+REG32(EXCL_ACCESS_CFG2, 0x29C)
+REG32(EXCL_ACCESS_CFG3, 0x2A0)
+REG32(MODE_REG_READ, 0x2A4)
+REG32(LPDDR_CTRL0, 0x2A8)
+REG32(LPDDR_CTRL1, 0x2AC)
+REG32(LPDDR_CTRL2, 0x2B0)
+REG32(LPDDR_CTRL3, 0x2B4)
+
+
+#define ZYNQ_DDRCTRL_MMIO_SIZE 0x400
+#define ZYNQ_DDRCTRL_NUM_REG (ZYNQ_DDRCTRL_MMIO_SIZE / 4)
+
+typedef struct DDRCTRLState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ RegisterInfoArray *reg_array;
+ uint32_t reg[ZYNQ_DDRCTRL_NUM_REG];
+ RegisterInfo regs_info[ZYNQ_DDRCTRL_NUM_REG];
+} DDRCTRLState;
+#endif
--
2.50.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 10/14] hw/misc/zynq_slcr: Add logic for DCI configuration
2025-08-15 9:00 [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Corvin Köhne
` (8 preceding siblings ...)
2025-08-15 9:01 ` [PATCH v2 09/14] hw/misc: Add dummy ZYNQ DDR controller Corvin Köhne
@ 2025-08-15 9:01 ` Corvin Köhne
2025-08-24 16:41 ` Edgar E. Iglesias
2025-08-15 9:01 ` [PATCH v2 11/14] hw/misc: Add Beckhoff CCAT device Corvin Köhne
` (4 subsequent siblings)
14 siblings, 1 reply; 29+ messages in thread
From: Corvin Köhne @ 2025-08-15 9:01 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Maydell, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block, YannickV,
Edgar E. Iglesias
From: YannickV <Y.Vossen@beckhoff.com>
The registers for the digitally controlled impedance (DCI) clock are
part of the system level control registers (SLCR). The DONE bit in
the status register indicates a successfull DCI calibration. An
description of the calibration process can be found here:
https://docs.amd.com/r/en-US/ug585-zynq-7000-SoC-TRM/DDR-IOB-Impedance-Calibration
The DCI control register and status register have been added. As soon
as the ENABLE and RESET bit are set, the RESET bit has also been toggled
to 0 before and the UPDATE_CONTROL is not set, the DONE bit in the status
register is set. If these bits change the DONE bit is reset. Note that the
option bits are not taken into consideration.
Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
Reviewed-by: Edgar E. Iglesias <edgar.iglesias@amd.com>
---
hw/misc/zynq_slcr.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/hw/misc/zynq_slcr.c b/hw/misc/zynq_slcr.c
index a766bab182..8d15f0cc66 100644
--- a/hw/misc/zynq_slcr.c
+++ b/hw/misc/zynq_slcr.c
@@ -180,6 +180,12 @@ REG32(GPIOB_CFG_HSTL, 0xb14)
REG32(GPIOB_DRVR_BIAS_CTRL, 0xb18)
REG32(DDRIOB, 0xb40)
+REG32(DDRIOB_DCI_CTRL, 0xb70)
+ FIELD(DDRIOB_DCI_CTRL, RESET, 0, 1)
+ FIELD(DDRIOB_DCI_CTRL, ENABLE, 1, 1)
+ FIELD(DDRIOB_DCI_CTRL, UPDATE_CONTROL, 20, 1)
+REG32(DDRIOB_DCI_STATUS, 0xb74)
+ FIELD(DDRIOB_DCI_STATUS, DONE, 13, 1)
#define DDRIOB_LENGTH 14
#define ZYNQ_SLCR_MMIO_SIZE 0x1000
@@ -193,6 +199,8 @@ struct ZynqSLCRState {
MemoryRegion iomem;
+ bool ddriob_dci_ctrl_reset_toggled;
+
uint32_t regs[ZYNQ_SLCR_NUM_REGS];
Clock *ps_clk;
@@ -331,6 +339,8 @@ static void zynq_slcr_reset_init(Object *obj, ResetType type)
DB_PRINT("RESET\n");
+ s->ddriob_dci_ctrl_reset_toggled = false;
+
s->regs[R_LOCKSTA] = 1;
/* 0x100 - 0x11C */
s->regs[R_ARM_PLL_CTRL] = 0x0001A008;
@@ -418,6 +428,8 @@ static void zynq_slcr_reset_init(Object *obj, ResetType type)
s->regs[R_DDRIOB + 4] = s->regs[R_DDRIOB + 5] = s->regs[R_DDRIOB + 6]
= 0x00000e00;
s->regs[R_DDRIOB + 12] = 0x00000021;
+
+ s->regs[R_DDRIOB_DCI_CTRL] = 0x00000020;
}
static void zynq_slcr_reset_hold(Object *obj, ResetType type)
@@ -554,6 +566,25 @@ static void zynq_slcr_write(void *opaque, hwaddr offset,
(int)offset, (unsigned)val & 0xFFFF);
}
return;
+
+ case R_DDRIOB_DCI_CTRL:
+ if (!FIELD_EX32(val, DDRIOB_DCI_CTRL, RESET) &&
+ FIELD_EX32(s->regs[R_DDRIOB_DCI_CTRL], DDRIOB_DCI_CTRL, RESET)) {
+
+ s->ddriob_dci_ctrl_reset_toggled = true;
+ DB_PRINT("DDRIOB DCI CTRL RESET was toggled\n");
+ }
+
+ if (FIELD_EX32(val, DDRIOB_DCI_CTRL, ENABLE) &&
+ FIELD_EX32(val, DDRIOB_DCI_CTRL, RESET) &&
+ !FIELD_EX32(val, DDRIOB_DCI_CTRL, UPDATE_CONTROL) &&
+ s->ddriob_dci_ctrl_reset_toggled) {
+
+ s->regs[R_DDRIOB_DCI_STATUS] |= R_DDRIOB_DCI_STATUS_DONE_MASK;
+ } else {
+ s->regs[R_DDRIOB_DCI_STATUS] &= ~R_DDRIOB_DCI_STATUS_DONE_MASK;
+ }
+ break;
}
if (s->regs[R_LOCKSTA]) {
--
2.50.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 11/14] hw/misc: Add Beckhoff CCAT device
2025-08-15 9:00 [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Corvin Köhne
` (9 preceding siblings ...)
2025-08-15 9:01 ` [PATCH v2 10/14] hw/misc/zynq_slcr: Add logic for DCI configuration Corvin Köhne
@ 2025-08-15 9:01 ` Corvin Köhne
2025-08-19 16:03 ` Peter Maydell
2025-08-15 9:01 ` [PATCH v2 12/14] hw/block/m25p80: Add HAS_SR_TB flag for is25lp016d Corvin Köhne
` (3 subsequent siblings)
14 siblings, 1 reply; 29+ messages in thread
From: Corvin Köhne @ 2025-08-15 9:01 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Maydell, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block, YannickV
From: YannickV <Y.Vossen@beckhoff.com>
This adds the Beckhoff Communication Controller (CCAT). The information
block, EEPROM interface and DMA controller are currently implemented.
The EEPROM provides production information for Beckhoff Devices.
An EEPORM binary must therefor be handed over. It should be aligned to
a power of two. If no EEPROM binary is handed over an empty EEPROM of
size 4096 is initialized.
This device is needed for the Beckhoff CX7200 board emulation.
Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
---
hw/misc/Kconfig | 3 +
hw/misc/beckhoff_ccat.c | 365 ++++++++++++++++++++++++++++++++++++++++
hw/misc/meson.build | 1 +
3 files changed, 369 insertions(+)
create mode 100644 hw/misc/beckhoff_ccat.c
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 99548e146f..f3a2efa350 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -223,4 +223,7 @@ config XLNX_VERSAL_TRNG
config XLNX_ZYNQ_DDRC
bool
+config BECKHOFF_CCAT
+ bool
+
source macio/Kconfig
diff --git a/hw/misc/beckhoff_ccat.c b/hw/misc/beckhoff_ccat.c
new file mode 100644
index 0000000000..0e21962a98
--- /dev/null
+++ b/hw/misc/beckhoff_ccat.c
@@ -0,0 +1,365 @@
+/*
+ * Beckhoff Communication Controller Emulation
+ *
+ * Copyright (c) Beckhoff Automation GmbH. & Co. KG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sysbus.h"
+#include "hw/register.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "system/block-backend.h"
+#include "exec/address-spaces.h"
+#include "exec/memory.h"
+#include "system/dma.h"
+#include "qemu/error-report.h"
+#include "block/block.h"
+#include "block/block_int.h"
+#include "block/qdict.h"
+#include "hw/block/block.h"
+
+#ifndef CCAT_ERR_DEBUG
+#define CCAT_ERR_DEBUG 0
+#endif
+
+#define DB_PRINT_L(level, ...) do { \
+ if (CCAT_ERR_DEBUG > (level)) { \
+ fprintf(stderr, ": %s: ", __func__); \
+ fprintf(stderr, ## __VA_ARGS__); \
+ } \
+} while (0)
+
+#define DB_PRINT(...) DB_PRINT_L(0, ## __VA_ARGS__)
+
+#define TYPE_BECKHOFF_CCAT "beckhoff-ccat"
+#define BECKHOFF_CCAT(obj) \
+ OBJECT_CHECK(BeckhoffCcat, (obj), TYPE_BECKHOFF_CCAT)
+
+#define MAX_NUM_SLOTS 32
+
+#define CCAT_EEPROM_OFFSET 0x100
+#define CCAT_DMA_OFFSET 0x8000
+
+#define CCAT_MEM_SIZE 0xFFFF
+#define CCAT_DMA_SIZE 0x800
+#define CCAT_EEPROM_SIZE 0x20
+
+#define EEPROM_MEMORY_SIZE 0x1000
+
+#define EEPROM_CMD_OFFSET (CCAT_EEPROM_OFFSET + 0x00)
+ #define EEPROM_CMD_WRITE_MASK 0x2
+ #define EEPROM_CMD_READ_MASK 0x1
+#define EEPROM_ADR_OFFSET (CCAT_EEPROM_OFFSET + 0x04)
+#define EEPROM_DATA_OFFSET (CCAT_EEPROM_OFFSET + 0x08)
+
+#define DMA_BUFFER_OFFSET (CCAT_DMA_OFFSET + 0x00)
+#define DMA_DIRECTION_OFFSET (CCAT_DMA_OFFSET + 0x7c0)
+ #define DMA_DIRECTION_MASK 1
+#define DMA_TRANSFER_OFFSET (CCAT_DMA_OFFSET + 0x7c4)
+#define DMA_HOST_ADR_OFFSET (CCAT_DMA_OFFSET + 0x7c8)
+#define DMA_TRANSFER_LENGTH_OFFSET (CCAT_DMA_OFFSET + 0x7cc)
+
+/*
+ * The informationblock is always located at address 0x0.
+ * Address and size are therefor replaced by two identifiers.
+ * The Parameter give information about the maximal number of
+ * function slots and the creation date (in this case 01.01.2001)
+ */
+#define CCAT_ID_1 0x88a4
+#define CCAT_ID_2 0x54414343
+#define CCAT_INFO_BLOCK_PARAMS (MAX_NUM_SLOTS << 0) | (0x1 << 8) | \
+ (0x1 << 16) | (0x1 << 24)
+
+#define CCAT_FUN_TYPE_ENTRY 0x0001
+#define CCAT_FUN_TYPE_EEPROM 0x0012
+#define CCAT_FUN_TYPE_DMA 0x0013
+
+typedef struct BeckhoffCcat {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ uint8_t mem[CCAT_MEM_SIZE];
+
+ BlockBackend *eeprom_blk;
+ uint8_t *eeprom_storage;
+ int64_t eeprom_size;
+} BeckhoffCcat;
+
+typedef struct __attribute__((packed)) CcatFunctionBlock {
+ uint16_t type;
+ uint16_t revision;
+ uint32_t parameter;
+ uint32_t address_offset;
+ uint32_t size;
+} CcatFunctionBlock;
+
+static void sync_eeprom(BeckhoffCcat *s)
+{
+ if (!s->eeprom_blk) {
+ return;
+ }
+ blk_pwrite(s->eeprom_blk, 0, s->eeprom_size, s->eeprom_storage, 0);
+}
+
+static uint64_t beckhoff_ccat_eeprom_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ BeckhoffCcat *s = opaque;
+ uint64_t val = 0;
+ memcpy(&val, &s->mem[addr], size);
+ return val;
+}
+
+static void beckhoff_ccat_eeprom_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ BeckhoffCcat *s = opaque;
+ uint64_t eeprom_adr;
+ switch (addr) {
+ case EEPROM_CMD_OFFSET:
+ eeprom_adr = *(uint32_t *)&s->mem[EEPROM_ADR_OFFSET];
+ eeprom_adr = (eeprom_adr * 2) % s->eeprom_size;
+ if (val & EEPROM_CMD_READ_MASK) {
+ uint64_t buf = 0;
+ uint32_t bytes_to_read = 8;
+ if (eeprom_adr > s->eeprom_size - 8) {
+ bytes_to_read = s->eeprom_size - eeprom_adr;
+ }
+ memcpy(&buf, s->eeprom_storage + eeprom_adr, bytes_to_read);
+ *(uint64_t *)&s->mem[EEPROM_DATA_OFFSET] = buf;
+
+ } else if (val & EEPROM_CMD_WRITE_MASK) {
+ uint32_t buf = *(uint32_t *)&s->mem[EEPROM_DATA_OFFSET];
+ memcpy(s->eeprom_storage + eeprom_adr, &buf, 2);
+ sync_eeprom(s);
+ }
+ break;
+ default:
+ memcpy(&s->mem[addr], &val, size);
+ }
+}
+
+static uint64_t beckhoff_ccat_dma_read(void *opaque, hwaddr addr, unsigned size)
+{
+ BeckhoffCcat *s = opaque;
+ uint64_t val = 0;
+
+ switch (addr) {
+ case DMA_TRANSFER_OFFSET:
+ if (s->mem[DMA_TRANSFER_OFFSET] & 0x1) {
+ DB_PRINT("DMA transfer finished\n");
+ s->mem[DMA_TRANSFER_OFFSET] = 0;
+ }
+ break;
+ }
+ memcpy(&val, &s->mem[addr], size);
+ return val;
+}
+
+static void beckhoff_ccat_dma_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ BeckhoffCcat *s = opaque;
+ switch (addr) {
+ case DMA_TRANSFER_OFFSET:
+ uint8_t len = s->mem[DMA_TRANSFER_LENGTH_OFFSET];
+ uint8_t *mem_buf = &s->mem[DMA_BUFFER_OFFSET];
+
+ if (s->mem[DMA_DIRECTION_OFFSET] & DMA_DIRECTION_MASK) {
+ dma_addr_t dmaAddr = *(uint32_t *)&s->mem[DMA_HOST_ADR_OFFSET];
+ dma_memory_read(&address_space_memory, dmaAddr,
+ mem_buf, len * 8, MEMTXATTRS_UNSPECIFIED);
+ } else {
+ dma_addr_t dmaAddr = *(uint32_t *)&s->mem[DMA_HOST_ADR_OFFSET];
+ dma_memory_write(&address_space_memory, dmaAddr + 8,
+ mem_buf, len * 8, MEMTXATTRS_UNSPECIFIED);
+ }
+ break;
+ }
+ memcpy(&s->mem[addr], &val, size);
+}
+static uint64_t beckhoff_ccat_read(void *opaque, hwaddr addr, unsigned size)
+{
+ DB_PRINT("CCAT_READ addr=0x%lx size=%u\n", addr, size);
+
+ BeckhoffCcat *s = opaque;
+ uint64_t val = 0;
+
+ if (addr > CCAT_MEM_SIZE - size) {
+ error_report("Overflow. Address or size is too large.\n");
+ exit(1);
+ }
+
+ if (addr >= CCAT_EEPROM_OFFSET &&
+ addr <= CCAT_EEPROM_OFFSET + s->eeprom_size) {
+ return beckhoff_ccat_eeprom_read(opaque, addr, size);
+ } else if (addr >= CCAT_DMA_OFFSET &&
+ addr <= CCAT_DMA_OFFSET + CCAT_DMA_SIZE) {
+ return beckhoff_ccat_dma_read(opaque, addr, size);
+ } else {
+ memcpy(&val, &s->mem[addr], size);
+ }
+
+ return val;
+}
+
+static void beckhoff_ccat_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ DB_PRINT("CCAT_WRITE addr=0x%lx size=%u val=0x%lx\n", addr, size, val);
+
+ BeckhoffCcat *s = opaque;
+
+ if (addr > CCAT_MEM_SIZE - size) {
+ error_report("Overflow. Address or size is too large.\n");
+ exit(1);
+ }
+
+ if (addr >= CCAT_EEPROM_OFFSET &&
+ addr <= CCAT_EEPROM_OFFSET + s->eeprom_size) {
+ beckhoff_ccat_eeprom_write(opaque, addr, val, size);
+ } else if (addr >= CCAT_DMA_OFFSET &&
+ addr <= CCAT_DMA_OFFSET + CCAT_DMA_SIZE) {
+ beckhoff_ccat_dma_write(opaque, addr, val, size);
+ } else {
+ memcpy(&s->mem[addr], &val, size);
+ }
+}
+
+static const MemoryRegionOps beckhoff_ccat_ops = {
+ .read = beckhoff_ccat_read,
+ .write = beckhoff_ccat_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 8,
+ },
+};
+
+
+static void beckhoff_ccat_reset(DeviceState *dev)
+{
+ BeckhoffCcat *s = BECKHOFF_CCAT(dev);
+
+ CcatFunctionBlock function_blocks[MAX_NUM_SLOTS] = {0};
+
+ CcatFunctionBlock info_block = {
+ .type = CCAT_FUN_TYPE_ENTRY,
+ .revision = 0x0001,
+ .parameter = CCAT_INFO_BLOCK_PARAMS,
+ .address_offset = CCAT_ID_1,
+ .size = CCAT_ID_2
+ };
+ CcatFunctionBlock eeprom_block = {
+ .type = CCAT_FUN_TYPE_EEPROM,
+ .revision = 0x0001,
+ .parameter = 0,
+ .address_offset = CCAT_EEPROM_OFFSET,
+ .size = CCAT_EEPROM_SIZE
+ };
+ CcatFunctionBlock dma_block = {
+ .type = CCAT_FUN_TYPE_DMA,
+ .revision = 0x0000,
+ .parameter = 0,
+ .address_offset = CCAT_DMA_OFFSET,
+ .size = CCAT_DMA_SIZE
+ };
+
+ /*
+ * The EEPROM interface is usually at function slot 11.
+ * The DMA controller is usually at function slot 15.
+ */
+ function_blocks[0] = info_block;
+ function_blocks[11] = eeprom_block;
+ function_blocks[15] = dma_block;
+
+ memcpy(&s->mem[0], function_blocks, sizeof(function_blocks));
+}
+
+static void beckhoff_ccat_realize(DeviceState *dev, Error **errp)
+{
+ BeckhoffCcat *s = BECKHOFF_CCAT(dev);
+ BlockBackend *blk;
+
+ blk = blk_by_name("ccat-eeprom");
+
+ if (blk) {
+ uint64_t blk_size = blk_getlength(blk);
+ if (!is_power_of_2(blk_size)) {
+ error_report("Blockend size is not a power of two.");
+ }
+
+ if (blk_size < 512) {
+ error_report("Blockend size is too small. Using backup.");
+ s->eeprom_size = EEPROM_MEMORY_SIZE;
+ s->eeprom_storage = blk_blockalign(NULL, s->eeprom_size);
+ memset(s->eeprom_storage, 0x00, s->eeprom_size);
+ } else {
+ DB_PRINT("EEPROM block backend found\n");
+ blk_set_perm(blk, BLK_PERM_WRITE, BLK_PERM_ALL, errp);
+
+ s->eeprom_size = blk_size;
+ s->eeprom_blk = blk;
+ s->eeprom_storage = blk_blockalign(s->eeprom_blk, s->eeprom_size);
+
+ if (!blk_check_size_and_read_all(s->eeprom_blk, DEVICE(s),
+ s->eeprom_storage, s->eeprom_size,
+ errp)) {
+ exit(1);
+ }
+ }
+ } else {
+ s->eeprom_size = EEPROM_MEMORY_SIZE;
+ s->eeprom_storage = blk_blockalign(NULL, s->eeprom_size);
+ memset(s->eeprom_storage, 0x00, s->eeprom_size);
+ }
+
+ beckhoff_ccat_reset(dev);
+}
+
+static void beckhoff_ccat_init(Object *obj)
+{
+ BeckhoffCcat *s = BECKHOFF_CCAT(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ memory_region_init_io(&s->iomem, obj, &beckhoff_ccat_ops, s,
+ TYPE_BECKHOFF_CCAT, CCAT_MEM_SIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static void beckhoff_ccat_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ dc->realize = beckhoff_ccat_realize;
+}
+
+static const TypeInfo beckhoff_ccat_info = {
+ .name = TYPE_BECKHOFF_CCAT,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(BeckhoffCcat),
+ .class_init = beckhoff_ccat_class_init,
+ .instance_init = beckhoff_ccat_init,
+};
+
+static void beckhoff_ccat_register_types(void)
+{
+ type_register_static(&beckhoff_ccat_info);
+}
+
+type_init(beckhoff_ccat_register_types)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 6ee7b6c71d..1fc1468464 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -14,6 +14,7 @@ system_ss.add(when: 'CONFIG_PL310', if_true: files('arm_l2x0.c'))
system_ss.add(when: 'CONFIG_INTEGRATOR_DEBUG', if_true: files('arm_integrator_debug.c'))
system_ss.add(when: 'CONFIG_A9SCU', if_true: files('a9scu.c'))
system_ss.add(when: 'CONFIG_ARM11SCU', if_true: files('arm11scu.c'))
+system_ss.add(when: 'CONFIG_BECKHOFF_CCAT', if_true: files('beckhoff_ccat.c'))
system_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m_ras.c'))
--
2.50.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 12/14] hw/block/m25p80: Add HAS_SR_TB flag for is25lp016d
2025-08-15 9:00 [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Corvin Köhne
` (10 preceding siblings ...)
2025-08-15 9:01 ` [PATCH v2 11/14] hw/misc: Add Beckhoff CCAT device Corvin Köhne
@ 2025-08-15 9:01 ` Corvin Köhne
2025-08-15 9:01 ` [PATCH v2 13/14] hw/arm: Add new machine based on xilinx-zynq-a9 for Beckhoff CX7200 Corvin Köhne
` (2 subsequent siblings)
14 siblings, 0 replies; 29+ messages in thread
From: Corvin Köhne @ 2025-08-15 9:01 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Maydell, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block, YannickV
From: YannickV <Y.Vossen@beckhoff.com>
The is25lp016d has 4 Block Write Protect Bits. BP3 specifies
whether the upper or lower range should be protected. Therefore,
we add the HAS_SR_TB flag to the is25lp016d flags.
Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
---
hw/block/m25p80.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/hw/block/m25p80.c b/hw/block/m25p80.c
index b84c6afb32..4c9d79ec44 100644
--- a/hw/block/m25p80.c
+++ b/hw/block/m25p80.c
@@ -217,7 +217,8 @@ static const FlashPartInfo known_devices[] = {
/* ISSI */
{ INFO("is25lq040b", 0x9d4013, 0, 64 << 10, 8, ER_4K) },
{ INFO("is25lp080d", 0x9d6014, 0, 64 << 10, 16, ER_4K) },
- { INFO("is25lp016d", 0x9d6015, 0, 64 << 10, 32, ER_4K) },
+ { INFO("is25lp016d", 0x9d6015, 0, 64 << 10, 32,
+ ER_4K | HAS_SR_TB) },
{ INFO("is25lp032", 0x9d6016, 0, 64 << 10, 64, ER_4K) },
{ INFO("is25lp064", 0x9d6017, 0, 64 << 10, 128, ER_4K) },
{ INFO("is25lp128", 0x9d6018, 0, 64 << 10, 256, ER_4K) },
--
2.50.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 13/14] hw/arm: Add new machine based on xilinx-zynq-a9 for Beckhoff CX7200
2025-08-15 9:00 [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Corvin Köhne
` (11 preceding siblings ...)
2025-08-15 9:01 ` [PATCH v2 12/14] hw/block/m25p80: Add HAS_SR_TB flag for is25lp016d Corvin Köhne
@ 2025-08-15 9:01 ` Corvin Köhne
2025-08-15 9:01 ` [PATCH v2 14/14] docs/system/arm: Add support " Corvin Köhne
2025-08-15 18:06 ` [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Peter Maydell
14 siblings, 0 replies; 29+ messages in thread
From: Corvin Köhne @ 2025-08-15 9:01 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Maydell, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block, YannickV
From: YannickV <Y.Vossen@beckhoff.com>
This commit introduces a new machine, derived from xilinx-zynq-a9.
While retaining the foundational architecture, unnecessary peripherals
have been removed and the remaining peripherals have been adapted to
match the CX7200's hardware layout and behavior.
Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
---
hw/arm/Kconfig | 18 ++
hw/arm/beckhoff_CX7200.c | 440 +++++++++++++++++++++++++++++++++++++++
hw/arm/meson.build | 1 +
3 files changed, 459 insertions(+)
create mode 100644 hw/arm/beckhoff_CX7200.c
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 256013ca80..66af990da9 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -310,6 +310,24 @@ config ZYNQ
select XILINX_SPIPS
select ZYNQ_DEVCFG
+config BECKHOFF_CX7200
+ bool
+ default y
+ depends on TCG && ARM
+ select A9MPCORE
+ select CADENCE # UART
+ select PFLASH_CFI02
+ select PL310 # cache controller
+ select PL330
+ select SDHCI
+ select SSI_M25P80
+ select XILINX # UART
+ select XILINX_AXI
+ select XILINX_SPI
+ select XILINX_SPIPS
+ select ZYNQ_DEVCFG
+ select BECKHOFF_CCAT
+
config ARM_V7M
bool
# currently v7M must be included in a TCG build due to translate.c
diff --git a/hw/arm/beckhoff_CX7200.c b/hw/arm/beckhoff_CX7200.c
new file mode 100644
index 0000000000..ba863e0100
--- /dev/null
+++ b/hw/arm/beckhoff_CX7200.c
@@ -0,0 +1,440 @@
+/*
+ * Modified Xilinx Zynq Baseboard System emulation for Beckhoff CX7200.
+ *
+ * Based on /hw/arm/xilinx_zynq.c:
+ * Copyright (c) 2010 Xilinx.
+ * Copyright (c) 2012 Peter A.G. Crosthwaite (peter.croshtwaite@petalogix.com)
+ * Copyright (c) 2012 Petalogix Pty Ltd.
+ * Original code by Haibing Ma.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
+#include "hw/sysbus.h"
+#include "hw/arm/boot.h"
+#include "net/net.h"
+#include "system/system.h"
+#include "hw/boards.h"
+#include "hw/block/flash.h"
+#include "hw/loader.h"
+#include "hw/adc/zynq-xadc.h"
+#include "hw/ssi/ssi.h"
+#include "qemu/error-report.h"
+#include "hw/sd/sdhci.h"
+#include "hw/char/cadence_uart.h"
+#include "hw/net/cadence_gem.h"
+#include "hw/cpu/a9mpcore.h"
+#include "hw/qdev-clock.h"
+#include "hw/misc/unimp.h"
+#include "system/reset.h"
+#include "qom/object.h"
+#include "exec/tswap.h"
+#include "target/arm/cpu-qom.h"
+#include "qapi/visitor.h"
+
+#define TYPE_CX7200_MACHINE MACHINE_TYPE_NAME("beckhoff-cx7200")
+OBJECT_DECLARE_SIMPLE_TYPE(CX7200MachineState, CX7200_MACHINE)
+
+/* board base frequency: 33.333333 MHz */
+#define PS_CLK_FREQUENCY (100 * 1000 * 1000 / 3)
+
+#define PERIPHCLK_PERIOD 2
+#define PS7_CPU_CLK_FREQUENCY 720000000
+
+#define NUM_SPI_FLASHES 0
+#define NUM_QSPI_FLASHES 1
+#define NUM_QSPI_BUSSES 1
+
+#define IRQ_OFFSET 32 /* pic interrupts start from index 32 */
+
+#define MPCORE_PERIPHBASE 0xF8F00000
+#define ZYNQ_BOARD_MIDR 0x413FC090
+
+static const int dma_irqs[8] = {
+ 46, 47, 48, 49, 72, 73, 74, 75
+};
+
+#define BOARD_SETUP_ADDR 0x100
+
+#define SLCR_LOCK_OFFSET 0x004
+#define SLCR_UNLOCK_OFFSET 0x008
+#define SLCR_ARM_PLL_OFFSET 0x100
+
+#define SLCR_XILINX_UNLOCK_KEY 0xdf0d
+#define SLCR_XILINX_LOCK_KEY 0x767b
+
+#define ZYNQ_SDHCI_CAPABILITIES 0x69ec0080 /* Datasheet: UG585 (v1.12.1) */
+
+#define ARMV7_IMM16(x) (extract32((x), 0, 12) | \
+ extract32((x), 12, 4) << 16)
+
+/*
+ * Write immediate val to address r0 + addr. r0 should contain base offset
+ * of the SLCR block. Clobbers r1.
+ */
+
+#define SLCR_WRITE(addr, val) \
+ 0xe3001000 + ARMV7_IMM16(extract32((val), 0, 16)), /* movw r1 ... */ \
+ 0xe3401000 + ARMV7_IMM16(extract32((val), 16, 16)), /* movt r1 ... */ \
+ 0xe5801000 + (addr)
+
+#define ZYNQ_MAX_CPUS 2
+
+struct CX7200MachineState {
+ MachineState parent;
+ Clock *ps_clk;
+ ARMCPU *cpu[ZYNQ_MAX_CPUS];
+ uint8_t boot_mode;
+};
+
+static void beckhoff_cx7200_write_board_setup(ARMCPU *cpu,
+ const struct arm_boot_info *info)
+{
+ int n;
+ uint32_t board_setup_blob[] = {
+ 0xe3a004f8, /* mov r0, #0xf8000000 */
+ SLCR_WRITE(SLCR_UNLOCK_OFFSET, SLCR_XILINX_UNLOCK_KEY),
+ SLCR_WRITE(SLCR_ARM_PLL_OFFSET, 0x00014008),
+ SLCR_WRITE(SLCR_LOCK_OFFSET, SLCR_XILINX_LOCK_KEY),
+ 0xe12fff1e, /* bx lr */
+ };
+ for (n = 0; n < ARRAY_SIZE(board_setup_blob); n++) {
+ board_setup_blob[n] = tswap32(board_setup_blob[n]);
+ }
+ rom_add_blob_fixed("board-setup", board_setup_blob,
+ sizeof(board_setup_blob), BOARD_SETUP_ADDR);
+}
+
+static struct arm_boot_info beckhoff_cx7200_binfo = {};
+
+static void gem_init(uint32_t base, qemu_irq irq)
+{
+ DeviceState *dev;
+ SysBusDevice *s;
+
+ dev = qdev_new(TYPE_CADENCE_GEM);
+ qemu_configure_nic_device(dev, true, NULL);
+ object_property_set_int(OBJECT(dev), "phy-addr", 7, &error_abort);
+ s = SYS_BUS_DEVICE(dev);
+ sysbus_realize_and_unref(s, &error_fatal);
+ sysbus_mmio_map(s, 0, base);
+ sysbus_connect_irq(s, 0, irq);
+}
+
+static void ccat_init(uint32_t base)
+{
+ DeviceState *dev;
+ SysBusDevice *busdev;
+
+ dev = qdev_new("beckhoff-ccat");
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_realize_and_unref(busdev, &error_fatal);
+ sysbus_mmio_map(busdev, 0, base);
+}
+
+static void ddr_ctrl_init(uint32_t base)
+{
+ DeviceState *dev;
+ SysBusDevice *busdev;
+
+ dev = qdev_new("zynq.ddr-ctlr");
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_realize_and_unref(busdev, &error_fatal);
+ sysbus_mmio_map(busdev, 0, base);
+}
+
+static inline int beckhoff_cx7200_init_spi_flashes(uint32_t base_addr,
+ qemu_irq irq, bool is_qspi, int unit0)
+{
+ int unit = unit0;
+ DeviceState *dev;
+ SysBusDevice *busdev;
+ SSIBus *spi;
+ DeviceState *flash_dev;
+ int i, j;
+ int num_busses = is_qspi ? NUM_QSPI_BUSSES : 1;
+ int num_ss = is_qspi ? NUM_QSPI_FLASHES : NUM_SPI_FLASHES;
+
+ dev = qdev_new(is_qspi ? "xlnx.ps7-qspi" : "xlnx.ps7-spi");
+ qdev_prop_set_uint8(dev, "num-txrx-bytes", is_qspi ? 4 : 1);
+ qdev_prop_set_uint8(dev, "num-ss-bits", num_ss);
+ qdev_prop_set_uint8(dev, "num-busses", num_busses);
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_realize_and_unref(busdev, &error_fatal);
+ sysbus_mmio_map(busdev, 0, base_addr);
+ if (is_qspi) {
+ sysbus_mmio_map(busdev, 1, 0xFC000000);
+ }
+ sysbus_connect_irq(busdev, 0, irq);
+
+ for (i = 0; i < num_busses; ++i) {
+ char bus_name[16];
+ qemu_irq cs_line;
+
+ snprintf(bus_name, 16, "spi%d", i);
+ spi = (SSIBus *)qdev_get_child_bus(dev, bus_name);
+
+ for (j = 0; j < num_ss; ++j) {
+ DriveInfo *dinfo = drive_get(IF_MTD, 0, unit++);
+ flash_dev = qdev_new("is25lp016d");
+ if (dinfo) {
+ qdev_prop_set_drive_err(flash_dev, "drive",
+ blk_by_legacy_dinfo(dinfo),
+ &error_fatal);
+ }
+ qdev_prop_set_uint8(flash_dev, "cs", j);
+ qdev_realize_and_unref(flash_dev, BUS(spi), &error_fatal);
+
+ cs_line = qdev_get_gpio_in_named(flash_dev, SSI_GPIO_CS, 0);
+ sysbus_connect_irq(busdev, i * num_ss + j + 1, cs_line);
+ }
+ }
+
+ return unit;
+}
+
+static void beckhoff_cx7200_set_boot_mode(Object *obj, const char *str,
+ Error **errp)
+{
+ CX7200MachineState *m = CX7200_MACHINE(obj);
+ uint8_t mode = 0;
+
+ if (!strncasecmp(str, "qspi", 4)) {
+ mode = 1;
+ } else if (!strncasecmp(str, "sd", 2)) {
+ mode = 5;
+ } else if (!strncasecmp(str, "nor", 3)) {
+ mode = 2;
+ } else if (!strncasecmp(str, "jtag", 4)) {
+ mode = 0;
+ } else {
+ error_setg(errp, "%s boot mode not supported", str);
+ return;
+ }
+ m->boot_mode = mode;
+}
+
+static void beckhoff_cx7200_init(MachineState *machine)
+{
+ CX7200MachineState *cx7200_machine = CX7200_MACHINE(machine);
+ MemoryRegion *address_space_mem = get_system_memory();
+ MemoryRegion *ocm_ram = g_new(MemoryRegion, 1);
+ DeviceState *carddev, *dev, *slcr;
+ SysBusDevice *busdev;
+ qemu_irq pic[64];
+ int n;
+ unsigned int smp_cpus = machine->smp.cpus;
+ DriveInfo *di;
+ BlockBackend *blk;
+
+ /* max 2GB ram */
+ if (machine->ram_size > 2 * GiB) {
+ error_report("RAM size more than 2 GiB is not supported");
+ exit(EXIT_FAILURE);
+ }
+
+ for (n = 0; n < smp_cpus; n++) {
+ Object *cpuobj = object_new(machine->cpu_type);
+
+ object_property_set_int(cpuobj, "midr", ZYNQ_BOARD_MIDR,
+ &error_fatal);
+ object_property_set_int(cpuobj, "reset-cbar", MPCORE_PERIPHBASE,
+ &error_fatal);
+
+ qdev_realize(DEVICE(cpuobj), NULL, &error_fatal);
+
+ cx7200_machine->cpu[n] = ARM_CPU(cpuobj);
+ }
+
+ /* DDR remapped to address zero. */
+ memory_region_add_subregion(address_space_mem, 0, machine->ram);
+
+ /* 256K of on-chip memory */
+ memory_region_init_ram(ocm_ram, NULL, "zynq.ocm_ram", 256 * KiB,
+ &error_fatal);
+ memory_region_add_subregion(address_space_mem, 0xFFFC0000, ocm_ram);
+
+ /* Create the main clock source, and feed slcr with it */
+ cx7200_machine->ps_clk = CLOCK(object_new(TYPE_CLOCK));
+ object_property_add_child(OBJECT(cx7200_machine), "ps_clk",
+ OBJECT(cx7200_machine->ps_clk));
+ object_unref(OBJECT(cx7200_machine->ps_clk));
+ clock_set_hz(cx7200_machine->ps_clk, PS_CLK_FREQUENCY);
+
+ /* Create slcr, keep a pointer to connect clocks */
+ slcr = qdev_new("xilinx-zynq_slcr");
+ qdev_connect_clock_in(slcr, "ps_clk", cx7200_machine->ps_clk);
+ qdev_prop_set_uint8(slcr, "boot-mode", cx7200_machine->boot_mode);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(slcr), &error_fatal);
+ sysbus_mmio_map(SYS_BUS_DEVICE(slcr), 0, 0xF8000000);
+
+ dev = qdev_new(TYPE_A9MPCORE_PRIV);
+ qdev_prop_set_uint32(dev, "num-cpu", smp_cpus);
+ A9MPPrivState *a9mp_priv_state = A9MPCORE_PRIV(dev);
+ a9mp_priv_state->gtimer.cpu_clk_freq_hz = PS7_CPU_CLK_FREQUENCY;
+ a9mp_priv_state->gtimer.periphclk_period = PERIPHCLK_PERIOD;
+ a9mp_priv_state->mptimer.clk_freq_hz = PS7_CPU_CLK_FREQUENCY;
+ a9mp_priv_state->mptimer.periphclk_period = PERIPHCLK_PERIOD;
+ a9mp_priv_state->wdt.clk_freq_hz = PS7_CPU_CLK_FREQUENCY;
+ a9mp_priv_state->wdt.periphclk_period = PERIPHCLK_PERIOD;
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_realize_and_unref(busdev, &error_fatal);
+ sysbus_mmio_map(busdev, 0, MPCORE_PERIPHBASE);
+ beckhoff_cx7200_binfo.gic_cpu_if_addr = MPCORE_PERIPHBASE + 0x100;
+ sysbus_create_varargs("l2x0", MPCORE_PERIPHBASE + 0x2000, NULL);
+ for (n = 0; n < smp_cpus; n++) {
+ /* See "hw/intc/arm_gic.h" for the IRQ line association */
+ DeviceState *cpudev = DEVICE(cx7200_machine->cpu[n]);
+ sysbus_connect_irq(busdev, n,
+ qdev_get_gpio_in(cpudev, ARM_CPU_IRQ));
+ sysbus_connect_irq(busdev, smp_cpus + n,
+ qdev_get_gpio_in(cpudev, ARM_CPU_FIQ));
+ }
+
+ for (n = 0; n < 64; n++) {
+ pic[n] = qdev_get_gpio_in(dev, n);
+ }
+
+ n = beckhoff_cx7200_init_spi_flashes(0xE0006000, pic[58 - IRQ_OFFSET],
+ false, 0);
+ n = beckhoff_cx7200_init_spi_flashes(0xE0007000, pic[81 - IRQ_OFFSET],
+ false, n);
+ n = beckhoff_cx7200_init_spi_flashes(0xE000D000, pic[51 - IRQ_OFFSET],
+ true, n);
+
+ dev = qdev_new(TYPE_CADENCE_UART);
+ busdev = SYS_BUS_DEVICE(dev);
+ qdev_prop_set_chr(dev, "chardev", serial_hd(0));
+ qdev_connect_clock_in(dev, "refclk",
+ qdev_get_clock_out(slcr, "uart0_ref_clk"));
+ sysbus_realize_and_unref(busdev, &error_fatal);
+ sysbus_mmio_map(busdev, 0, 0xE0000000);
+ sysbus_connect_irq(busdev, 0, pic[59 - IRQ_OFFSET]);
+ dev = qdev_new(TYPE_CADENCE_UART);
+ busdev = SYS_BUS_DEVICE(dev);
+ qdev_prop_set_chr(dev, "chardev", serial_hd(1));
+ qdev_connect_clock_in(dev, "refclk",
+ qdev_get_clock_out(slcr, "uart1_ref_clk"));
+ sysbus_realize_and_unref(busdev, &error_fatal);
+ sysbus_mmio_map(busdev, 0, 0xE0001000);
+ sysbus_connect_irq(busdev, 0, pic[82 - IRQ_OFFSET]);
+
+ sysbus_create_varargs("cadence_ttc", 0xF8001000, pic[42 - IRQ_OFFSET],
+ pic[43 - IRQ_OFFSET], pic[44 - IRQ_OFFSET], NULL);
+ sysbus_create_varargs("cadence_ttc", 0xF8002000, pic[69 - IRQ_OFFSET],
+ pic[70 - IRQ_OFFSET], pic[71 - IRQ_OFFSET], NULL);
+
+ gem_init(0xE000C000, pic[77 - IRQ_OFFSET]);
+
+ ccat_init(0x40000000);
+
+ ddr_ctrl_init(0xF8006000);
+
+ /*
+ * Compatible with:
+ * - SD Host Controller Specification Version 2.0 Part A2
+ * - SDIO Specification Version 2.0
+ * - MMC Specification Version 3.31
+ */
+ dev = qdev_new(TYPE_SYSBUS_SDHCI);
+ qdev_prop_set_uint8(dev, "sd-spec-version", 2);
+ qdev_prop_set_uint64(dev, "capareg", ZYNQ_SDHCI_CAPABILITIES);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xE0101000);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[79 - IRQ_OFFSET]);
+
+ di = drive_get(IF_SD, 0, 0);
+ blk = di ? blk_by_legacy_dinfo(di) : NULL;
+ carddev = qdev_new(TYPE_SD_CARD);
+ qdev_prop_set_drive_err(carddev, "drive", blk, &error_fatal);
+ qdev_realize_and_unref(carddev, qdev_get_child_bus(dev, "sd-bus"),
+ &error_fatal);
+
+ dev = qdev_new(TYPE_ZYNQ_XADC);
+ sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+ sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xF8007100);
+ sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[39 - IRQ_OFFSET]);
+
+ dev = qdev_new("pl330");
+ object_property_set_link(OBJECT(dev), "memory",
+ OBJECT(address_space_mem),
+ &error_fatal);
+ qdev_prop_set_uint8(dev, "num_chnls", 8);
+ qdev_prop_set_uint8(dev, "num_periph_req", 4);
+ qdev_prop_set_uint8(dev, "num_events", 16);
+
+ qdev_prop_set_uint8(dev, "data_width", 64);
+ qdev_prop_set_uint8(dev, "wr_cap", 8);
+ qdev_prop_set_uint8(dev, "wr_q_dep", 16);
+ qdev_prop_set_uint8(dev, "rd_cap", 8);
+ qdev_prop_set_uint8(dev, "rd_q_dep", 16);
+ qdev_prop_set_uint16(dev, "data_buffer_dep", 256);
+
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_realize_and_unref(busdev, &error_fatal);
+ sysbus_mmio_map(busdev, 0, 0xF8003000);
+ sysbus_connect_irq(busdev, 0, pic[45 - IRQ_OFFSET]); /* abort irq line */
+ for (n = 0; n < ARRAY_SIZE(dma_irqs); ++n) { /* event irqs */
+ sysbus_connect_irq(busdev, n + 1, pic[dma_irqs[n] - IRQ_OFFSET]);
+ }
+
+ dev = qdev_new("xlnx.ps7-dev-cfg");
+ busdev = SYS_BUS_DEVICE(dev);
+ sysbus_realize_and_unref(busdev, &error_fatal);
+ sysbus_connect_irq(busdev, 0, pic[40 - IRQ_OFFSET]);
+ sysbus_mmio_map(busdev, 0, 0xF8007000);
+
+ beckhoff_cx7200_binfo.ram_size = machine->ram_size;
+ beckhoff_cx7200_binfo.board_id = 0xd32;
+ beckhoff_cx7200_binfo.loader_start = 0;
+ beckhoff_cx7200_binfo.board_setup_addr = BOARD_SETUP_ADDR;
+ beckhoff_cx7200_binfo.write_board_setup = beckhoff_cx7200_write_board_setup;
+
+ arm_load_kernel(cx7200_machine->cpu[0], machine, &beckhoff_cx7200_binfo);
+}
+
+static void beckhoff_cx7200_machine_class_init(ObjectClass *oc, void *data)
+{
+ static const char * const valid_cpu_types[] = {
+ ARM_CPU_TYPE_NAME("cortex-a9"),
+ NULL
+ };
+ MachineClass *mc = MACHINE_CLASS(oc);
+ ObjectProperty *prop;
+ mc->desc = "Beckhoff IPC based on the Xilinx Zynq Platform Baseboard";
+ mc->init = beckhoff_cx7200_init;
+ mc->max_cpus = ZYNQ_MAX_CPUS;
+ mc->no_sdcard = 1;
+ mc->ignore_memory_transaction_failures = true;
+ mc->valid_cpu_types = valid_cpu_types;
+ mc->default_ram_id = "zynq.ext_ram";
+ prop = object_class_property_add_str(oc, "boot-mode", NULL,
+ beckhoff_cx7200_set_boot_mode);
+ object_class_property_set_description(oc, "boot-mode",
+ "Supported boot modes:"
+ " jtag qspi sd nor");
+ object_property_set_default_str(prop, "qspi");
+}
+
+static const TypeInfo beckhoff_cx7200_machine_type = {
+ .name = TYPE_CX7200_MACHINE,
+ .parent = TYPE_MACHINE,
+ .class_init = beckhoff_cx7200_machine_class_init,
+ .instance_size = sizeof(CX7200MachineState),
+};
+
+static void beckhoff_cx7200_machine_register_types(void)
+{
+ type_register_static(&beckhoff_cx7200_machine_type);
+}
+
+type_init(beckhoff_cx7200_machine_register_types)
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index 490234b3b8..b774b066dd 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -17,6 +17,7 @@ arm_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa-ref.c'))
arm_ss.add(when: 'CONFIG_STELLARIS', if_true: files('stellaris.c'))
arm_ss.add(when: 'CONFIG_STM32VLDISCOVERY', if_true: files('stm32vldiscovery.c'))
arm_ss.add(when: 'CONFIG_ZYNQ', if_true: files('xilinx_zynq.c'))
+arm_ss.add(when: 'CONFIG_BECKHOFF_CX7200', if_true: files('beckhoff_CX7200.c'))
arm_ss.add(when: 'CONFIG_SABRELITE', if_true: files('sabrelite.c'))
arm_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m.c'))
--
2.50.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v2 14/14] docs/system/arm: Add support for Beckhoff CX7200
2025-08-15 9:00 [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Corvin Köhne
` (12 preceding siblings ...)
2025-08-15 9:01 ` [PATCH v2 13/14] hw/arm: Add new machine based on xilinx-zynq-a9 for Beckhoff CX7200 Corvin Köhne
@ 2025-08-15 9:01 ` Corvin Köhne
2025-08-15 18:06 ` [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Peter Maydell
14 siblings, 0 replies; 29+ messages in thread
From: Corvin Köhne @ 2025-08-15 9:01 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Maydell, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block, YannickV
From: YannickV <Y.Vossen@beckhoff.com>
This commit offers some documentation on the Beckhoff CX7200
qemu emulation.
Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
---
docs/system/arm/beckhoff-cx7200.rst | 57 +++++++++++++++++++++++++++++
docs/system/target-arm.rst | 1 +
2 files changed, 58 insertions(+)
create mode 100644 docs/system/arm/beckhoff-cx7200.rst
diff --git a/docs/system/arm/beckhoff-cx7200.rst b/docs/system/arm/beckhoff-cx7200.rst
new file mode 100644
index 0000000000..f060319b0f
--- /dev/null
+++ b/docs/system/arm/beckhoff-cx7200.rst
@@ -0,0 +1,57 @@
+Beckhoff CX7200 (``beckhoff-cx7200``)
+======================================
+The Beckhoff CX7200 is based on the same architecture as the Xilinx Zynq A9.
+The Zynq 7000 family is based on the AMD SoC architecture. These products
+integrate a feature-rich dual or single-core Arm Cortex-A9 MPCore based
+processing system (PS) and AMD programmable logic (PL) in a single device.
+The Beckhoff Communication Controller (CCAT) can be found in the PL of Zynq.
+
+More details here:
+https://docs.amd.com/r/en-US/ug585-zynq-7000-SoC-TRM/Zynq-7000-SoC-Technical-Reference-Manual
+https://www.beckhoff.com/de-de/produkte/ipc/embedded-pcs/cx7000-arm-r-cortex-r/cx7293.html
+
+The CX7200 supports following devices:
+ - A9 MPCORE
+ - cortex-a9
+ - GIC v1
+ - Generic timer
+ - wdt
+ - OCM 256KB
+ - SMC SRAM@0xe2000000 64MB
+ - Zynq SLCR
+ - SPI x2
+ - QSPI
+ - UART
+ - TTC x2
+ - Gigabit Ethernet Controller
+ - SD Controller
+ - XADC
+ - Arm PrimeCell DMA Controller
+ - DDR Memory
+ - DDR Controller
+ - Beckhoff Communication Controller (CCAT)
+ - EEPROM Interface
+ - DMA Controller
+
+Following devices are not supported:
+ - I2C
+
+Running
+"""""""
+Directly loading an ELF file to the CPU of the CX7200 to run f.e. TC/RTOS (based on FreeRTOS):
+
+.. code-block:: bash
+
+ $ qemu-system-arm -M beckhoff-cx7200 \
+ -device loader,file=CX7200_Zynq_Fsbl.elf \
+ -display none \
+ -icount shift=auto \
+
+
+For setting the EEPROM content of the CCAT provide the following on the command line:
+
+.. code-block:: bash
+
+ -drive file=eeprom.bin,format=raw,id=ccat-eeprom
+
+The size of eeprom.bin must be aligned to a power of 2 and bigger than 256 bytes.
diff --git a/docs/system/target-arm.rst b/docs/system/target-arm.rst
index 9aaa9c414c..db7707725d 100644
--- a/docs/system/target-arm.rst
+++ b/docs/system/target-arm.rst
@@ -106,6 +106,7 @@ Board-specific documentation
arm/xlnx-versal-virt
arm/xlnx-zynq
arm/xlnx-zcu102
+ arm/beckhoff-cx7200
Emulated CPU architecture support
=================================
--
2.50.1
^ permalink raw reply related [flat|nested] 29+ messages in thread
* Re: [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board
2025-08-15 9:00 [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Corvin Köhne
` (13 preceding siblings ...)
2025-08-15 9:01 ` [PATCH v2 14/14] docs/system/arm: Add support " Corvin Köhne
@ 2025-08-15 18:06 ` Peter Maydell
2025-08-19 16:40 ` Peter Maydell
14 siblings, 1 reply; 29+ messages in thread
From: Peter Maydell @ 2025-08-15 18:06 UTC (permalink / raw)
To: Corvin Köhne
Cc: qemu-devel, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block
On Fri, 15 Aug 2025 at 10:01, Corvin Köhne <corvin.koehne@gmail.com> wrote:
>
> From: Corvin Köhne <c.koehne@beckhoff.com>
>
> Hi,
>
> Beckhoff has build a board, called CX7200, based on the Xilinx Zynq A9
> platform. This commit series adds the Beckhoff CX7200 as new board variant to
> QEMU.
>
> The emulation is able to successfully boot an CX7200 image. The image includes
> some self tests executed on every boot. Only the cache self test fails due to
> QEMU emulating the cache as always being coherent. The self tests include f.e.:
>
> * Network
> * Flash
> * CCAT DMA + EEPROM [1]
> * TwinCAT (Beckhoff's automation control software [2])
>
> [1] https://github.com/beckhoff/ccat
> [2] https://www.beckhoff.com/en-us/products/automation/
>
> YannickV (14):
> hw/timer: Make frequency configurable
> hw/timer: Make PERIPHCLK period configurable
> hw/dma/zynq-devcfg: Handle bitstream loading via DMA to 0xffffffff
> hw/arm/zynq-devcfg: Prevent unintended unlock during initialization
> hw/dma/zynq: Ensure PCFG_DONE bit remains set to indicate PL is in
> user mode
> hw/dma/zynq-devcfg: Simulate dummy PL reset
> hw/dma/zynq-devcfg: Indicate power-up status of PL
> hw/dma/zynq-devcfg: Fix register memory
> hw/misc: Add dummy ZYNQ DDR controller
> hw/misc/zynq_slcr: Add logic for DCI configuration
> hw/misc: Add Beckhoff CCAT device
> hw/block/m25p80: Add HAS_SR_TB flag for is25lp016d
> hw/arm: Add new machine based on xilinx-zynq-a9 for Beckhoff CX7200
> docs/system/arm: Add support for Beckhoff CX7200
This patchset is on my list to review. As an initial request,
for a new board could we have a test in tests/functional/
please? This is basically a simple test that downloads
a guest image from some public stable URL, runs it on QEMU,
and checks for some output on the UART that indicates
that it succeeded. (You can do more complex things like
sending commands to the guest if you want/need to, but
"does it basically boot" is the minimum bar here.)
The other files in tests/functional/ should hopefully
serve as examples you can pattern your test on.
thanks
-- PMM
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2 09/14] hw/misc: Add dummy ZYNQ DDR controller
2025-08-15 9:01 ` [PATCH v2 09/14] hw/misc: Add dummy ZYNQ DDR controller Corvin Köhne
@ 2025-08-19 15:43 ` Peter Maydell
2025-08-24 16:24 ` Edgar E. Iglesias
0 siblings, 1 reply; 29+ messages in thread
From: Peter Maydell @ 2025-08-19 15:43 UTC (permalink / raw)
To: Corvin Köhne
Cc: qemu-devel, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block
On Fri, 15 Aug 2025 at 10:01, Corvin Köhne <corvin.koehne@gmail.com> wrote:
>
> From: YannickV <Y.Vossen@beckhoff.com>
>
> A dummy DDR controller for ZYNQ has been added. While all registers are present,
> not all are functional. Read and write access is validated, and the user mode
> can be set. This provides a basic DDR controller initialization, preventing
> system hangs due to endless polling or similar issues.
>
> Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
> ---
> hw/misc/Kconfig | 3 +
> hw/misc/meson.build | 1 +
> hw/misc/xlnx-zynq-ddrc.c | 393 +++++++++++++++++++++++++++++++
> include/hw/misc/xlnx-zynq-ddrc.h | 140 +++++++++++
> 4 files changed, 537 insertions(+)
> create mode 100644 hw/misc/xlnx-zynq-ddrc.c
> create mode 100644 include/hw/misc/xlnx-zynq-ddrc.h
>
> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
> index 8f9ce2f68c..99548e146f 100644
> --- a/hw/misc/Kconfig
> +++ b/hw/misc/Kconfig
> @@ -220,4 +220,7 @@ config IOSB
> config XLNX_VERSAL_TRNG
> bool
>
> +config XLNX_ZYNQ_DDRC
> + bool
> +
> source macio/Kconfig
> diff --git a/hw/misc/meson.build b/hw/misc/meson.build
> index 55f493521b..6ee7b6c71d 100644
> --- a/hw/misc/meson.build
> +++ b/hw/misc/meson.build
> @@ -89,6 +89,7 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
> ))
> system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
> system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
> +system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('xlnx-zynq-ddrc.c'))
> system_ss.add(when: 'CONFIG_XLNX_ZYNQMP_ARM', if_true: files('xlnx-zynqmp-crf.c'))
> system_ss.add(when: 'CONFIG_XLNX_ZYNQMP_ARM', if_true: files('xlnx-zynqmp-apu-ctrl.c'))
> system_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files(
> diff --git a/hw/misc/xlnx-zynq-ddrc.c b/hw/misc/xlnx-zynq-ddrc.c
> new file mode 100644
> index 0000000000..8151a0e3ee
> --- /dev/null
> +++ b/hw/misc/xlnx-zynq-ddrc.c
All new files must start with a comment which at least
(a) says what the file is for and (b) has the SPDX
license indicator. You can optionally also add an authorship
or copyright line here if you/your employer need that.
Same applies to the .h file.
> +static void zynq_ddrctrl_class_init(ObjectClass *klass, void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> +
> + device_class_set_legacy_reset(dc, zynq_ddrctrl_reset);
> +}
This looks like it's missing registration of a vmstate
struct for state save/load.
thanks
-- PMM
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2 11/14] hw/misc: Add Beckhoff CCAT device
2025-08-15 9:01 ` [PATCH v2 11/14] hw/misc: Add Beckhoff CCAT device Corvin Köhne
@ 2025-08-19 16:03 ` Peter Maydell
0 siblings, 0 replies; 29+ messages in thread
From: Peter Maydell @ 2025-08-19 16:03 UTC (permalink / raw)
To: Corvin Köhne
Cc: qemu-devel, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block
On Fri, 15 Aug 2025 at 10:01, Corvin Köhne <corvin.koehne@gmail.com> wrote:
>
> From: YannickV <Y.Vossen@beckhoff.com>
>
> This adds the Beckhoff Communication Controller (CCAT). The information
> block, EEPROM interface and DMA controller are currently implemented.
>
> The EEPROM provides production information for Beckhoff Devices.
> An EEPORM binary must therefor be handed over. It should be aligned to
> a power of two. If no EEPROM binary is handed over an empty EEPROM of
> size 4096 is initialized.
>
> This device is needed for the Beckhoff CX7200 board emulation.
>
> Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
> ---
> hw/misc/Kconfig | 3 +
> hw/misc/beckhoff_ccat.c | 365 ++++++++++++++++++++++++++++++++++++++++
> hw/misc/meson.build | 1 +
> 3 files changed, 369 insertions(+)
> create mode 100644 hw/misc/beckhoff_ccat.c
>
> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
> index 99548e146f..f3a2efa350 100644
> --- a/hw/misc/Kconfig
> +++ b/hw/misc/Kconfig
> @@ -223,4 +223,7 @@ config XLNX_VERSAL_TRNG
> config XLNX_ZYNQ_DDRC
> bool
>
> +config BECKHOFF_CCAT
> + bool
> +
> source macio/Kconfig
> diff --git a/hw/misc/beckhoff_ccat.c b/hw/misc/beckhoff_ccat.c
> new file mode 100644
> index 0000000000..0e21962a98
> --- /dev/null
> +++ b/hw/misc/beckhoff_ccat.c
> @@ -0,0 +1,365 @@
> +/*
> + * Beckhoff Communication Controller Emulation
> + *
> + * Copyright (c) Beckhoff Automation GmbH. & Co. KG
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License as published by the
> + * Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
> + * for more details.
> + *
> + * You should have received a copy of the GNU General Public License along
> + * with this program; if not, see <http://www.gnu.org/licenses/>.
This needs an SPDX license identifier line. If you have that,
then you don't need the human-readable license blurb.
Is there a datasheet/manual for this device? The comment
at the top of the file is a good place to put the URL to it.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/sysbus.h"
> +#include "hw/register.h"
> +#include "qemu/bitops.h"
> +#include "qemu/log.h"
> +#include "qapi/error.h"
> +#include "system/block-backend.h"
> +#include "exec/address-spaces.h"
> +#include "exec/memory.h"
> +#include "system/dma.h"
> +#include "qemu/error-report.h"
> +#include "block/block.h"
> +#include "block/block_int.h"
> +#include "block/qdict.h"
> +#include "hw/block/block.h"
> +
> +#ifndef CCAT_ERR_DEBUG
> +#define CCAT_ERR_DEBUG 0
> +#endif
> +
> +#define DB_PRINT_L(level, ...) do { \
> + if (CCAT_ERR_DEBUG > (level)) { \
> + fprintf(stderr, ": %s: ", __func__); \
> + fprintf(stderr, ## __VA_ARGS__); \
> + } \
> +} while (0)
No debug printf macros in new code, please. Use either:
* tracepoints, for "this is information that might be
interesting for somebody debugging the device or code
that runs on the device"
* qemu_log_mask(LOG_UNIMP, ...), for "the guest tried
to use a feature that we don't implement"
* qemu_log_mask(LOG_GUEST_ERROR, ...), for "the guest
tried to do something that the datasheet says is
undefined behaviour/reserved/unpredictable"
> +
> +#define DB_PRINT(...) DB_PRINT_L(0, ## __VA_ARGS__)
> +
> +#define TYPE_BECKHOFF_CCAT "beckhoff-ccat"
> +#define BECKHOFF_CCAT(obj) \
> + OBJECT_CHECK(BeckhoffCcat, (obj), TYPE_BECKHOFF_CCAT)
Prefer the new-style OBJECT_DECLARE_SIMPLE_TYPE etc
macros, rather than defining the cast macro yourself.
> +
> +#define MAX_NUM_SLOTS 32
> +
> +#define CCAT_EEPROM_OFFSET 0x100
> +#define CCAT_DMA_OFFSET 0x8000
> +
> +#define CCAT_MEM_SIZE 0xFFFF
> +#define CCAT_DMA_SIZE 0x800
> +#define CCAT_EEPROM_SIZE 0x20
> +
> +#define EEPROM_MEMORY_SIZE 0x1000
> +
> +#define EEPROM_CMD_OFFSET (CCAT_EEPROM_OFFSET + 0x00)
> + #define EEPROM_CMD_WRITE_MASK 0x2
> + #define EEPROM_CMD_READ_MASK 0x1
> +#define EEPROM_ADR_OFFSET (CCAT_EEPROM_OFFSET + 0x04)
> +#define EEPROM_DATA_OFFSET (CCAT_EEPROM_OFFSET + 0x08)
> +
> +#define DMA_BUFFER_OFFSET (CCAT_DMA_OFFSET + 0x00)
> +#define DMA_DIRECTION_OFFSET (CCAT_DMA_OFFSET + 0x7c0)
> + #define DMA_DIRECTION_MASK 1
> +#define DMA_TRANSFER_OFFSET (CCAT_DMA_OFFSET + 0x7c4)
> +#define DMA_HOST_ADR_OFFSET (CCAT_DMA_OFFSET + 0x7c8)
> +#define DMA_TRANSFER_LENGTH_OFFSET (CCAT_DMA_OFFSET + 0x7cc)
> +
> +/*
> + * The informationblock is always located at address 0x0.
> + * Address and size are therefor replaced by two identifiers.
> + * The Parameter give information about the maximal number of
> + * function slots and the creation date (in this case 01.01.2001)
> + */
> +#define CCAT_ID_1 0x88a4
> +#define CCAT_ID_2 0x54414343
> +#define CCAT_INFO_BLOCK_PARAMS (MAX_NUM_SLOTS << 0) | (0x1 << 8) | \
> + (0x1 << 16) | (0x1 << 24)
> +
> +#define CCAT_FUN_TYPE_ENTRY 0x0001
> +#define CCAT_FUN_TYPE_EEPROM 0x0012
> +#define CCAT_FUN_TYPE_DMA 0x0013
> +
> +typedef struct BeckhoffCcat {
> + SysBusDevice parent_obj;
> +
> + MemoryRegion iomem;
> +
> + uint8_t mem[CCAT_MEM_SIZE];
> +
> + BlockBackend *eeprom_blk;
> + uint8_t *eeprom_storage;
> + int64_t eeprom_size;
> +} BeckhoffCcat;
> +
> +typedef struct __attribute__((packed)) CcatFunctionBlock {
> + uint16_t type;
> + uint16_t revision;
> + uint32_t parameter;
> + uint32_t address_offset;
> + uint32_t size;
> +} CcatFunctionBlock;
"attribute((packed))" is a bit of a red flag of doing something
wrong. In this case, you use this in beckhoff_ccat_reset() to
set up a host C structure that you memcpy() into s->mem[].
That is not portable to big-endian systems.
> +
> +static void sync_eeprom(BeckhoffCcat *s)
> +{
> + if (!s->eeprom_blk) {
> + return;
> + }
> + blk_pwrite(s->eeprom_blk, 0, s->eeprom_size, s->eeprom_storage, 0);
> +}
> +
> +static uint64_t beckhoff_ccat_eeprom_read(void *opaque, hwaddr addr,
> + unsigned size)
> +{
> + BeckhoffCcat *s = opaque;
> + uint64_t val = 0;
> + memcpy(&val, &s->mem[addr], size);
What is this trying to do ? If you want to read
N bytes of data in little endian order from a host
address, use ldn_le_p(). (Also available in "big endian"
and "host endianness" flavours.)
> + return val;
> +}
> +
> +static void beckhoff_ccat_eeprom_write(void *opaque, hwaddr addr, uint64_t val,
> + unsigned size)
> +{
> + BeckhoffCcat *s = opaque;
> + uint64_t eeprom_adr;
> + switch (addr) {
> + case EEPROM_CMD_OFFSET:
> + eeprom_adr = *(uint32_t *)&s->mem[EEPROM_ADR_OFFSET];
> + eeprom_adr = (eeprom_adr * 2) % s->eeprom_size;
> + if (val & EEPROM_CMD_READ_MASK) {
> + uint64_t buf = 0;
> + uint32_t bytes_to_read = 8;
> + if (eeprom_adr > s->eeprom_size - 8) {
> + bytes_to_read = s->eeprom_size - eeprom_adr;
> + }
> + memcpy(&buf, s->eeprom_storage + eeprom_adr, bytes_to_read);
> + *(uint64_t *)&s->mem[EEPROM_DATA_OFFSET] = buf;
All this code with casting to different pointer types and
using memcpy() looks very suspicious. Use the relevant
ld*_*_p() functions to load the right sized values at the
right endianness. (Casting from one pointer type to another
is usually an indication that you're going down a wrong path.)
> +
> + } else if (val & EEPROM_CMD_WRITE_MASK) {
> + uint32_t buf = *(uint32_t *)&s->mem[EEPROM_DATA_OFFSET];
> + memcpy(s->eeprom_storage + eeprom_adr, &buf, 2);
> + sync_eeprom(s);
> + }
> + break;
> + default:
> + memcpy(&s->mem[addr], &val, size);
> + }
> +}
> +
> +static uint64_t beckhoff_ccat_dma_read(void *opaque, hwaddr addr, unsigned size)
> +{
> + BeckhoffCcat *s = opaque;
> + uint64_t val = 0;
> +
> + switch (addr) {
> + case DMA_TRANSFER_OFFSET:
> + if (s->mem[DMA_TRANSFER_OFFSET] & 0x1) {
> + DB_PRINT("DMA transfer finished\n");
> + s->mem[DMA_TRANSFER_OFFSET] = 0;
> + }
> + break;
> + }
> + memcpy(&val, &s->mem[addr], size);
> + return val;
> +}
> +
> +static void beckhoff_ccat_dma_write(void *opaque, hwaddr addr, uint64_t val,
> + unsigned size)
> +{
> + BeckhoffCcat *s = opaque;
> + switch (addr) {
> + case DMA_TRANSFER_OFFSET:
> + uint8_t len = s->mem[DMA_TRANSFER_LENGTH_OFFSET];
> + uint8_t *mem_buf = &s->mem[DMA_BUFFER_OFFSET];
> +
> + if (s->mem[DMA_DIRECTION_OFFSET] & DMA_DIRECTION_MASK) {
> + dma_addr_t dmaAddr = *(uint32_t *)&s->mem[DMA_HOST_ADR_OFFSET];
Should probably be ldl_le_p().
> + dma_memory_read(&address_space_memory, dmaAddr,
> + mem_buf, len * 8, MEMTXATTRS_UNSPECIFIED);
> + } else {
> + dma_addr_t dmaAddr = *(uint32_t *)&s->mem[DMA_HOST_ADR_OFFSET];
> + dma_memory_write(&address_space_memory, dmaAddr + 8,
> + mem_buf, len * 8, MEMTXATTRS_UNSPECIFIED);
Does this really use addr for reads and addr + 8 for writes ?
> + }
> + break;
> + }
> + memcpy(&s->mem[addr], &val, size);
> +}
> +static uint64_t beckhoff_ccat_read(void *opaque, hwaddr addr, unsigned size)
> +{
> + DB_PRINT("CCAT_READ addr=0x%lx size=%u\n", addr, size);
> +
> + BeckhoffCcat *s = opaque;
> + uint64_t val = 0;
> +
> + if (addr > CCAT_MEM_SIZE - size) {
> + error_report("Overflow. Address or size is too large.\n");
> + exit(1);
Don't error_report() and exit for things like this.
Either:
(a) this is not possible unless there's a bug in QEMU:
in that case, use assert()
(b) this is possible if the guest does something silly:
in that case use qemu_log_mask(LOG_GUEST_ERROR, ...)
In this case it looks like an assert() is appropriate, because
the memory_region_init_io() size argument should ensure that
you can't get here with an addr + size that is beyond the
end of the block.
> + }
> +
> + if (addr >= CCAT_EEPROM_OFFSET &&
> + addr <= CCAT_EEPROM_OFFSET + s->eeprom_size) {
> + return beckhoff_ccat_eeprom_read(opaque, addr, size);
> + } else if (addr >= CCAT_DMA_OFFSET &&
> + addr <= CCAT_DMA_OFFSET + CCAT_DMA_SIZE) {
> + return beckhoff_ccat_dma_read(opaque, addr, size);
> + } else {
> + memcpy(&val, &s->mem[addr], size);
> + }
> +
> + return val;
> +}
> +
> +static void beckhoff_ccat_write(void *opaque, hwaddr addr, uint64_t val,
> + unsigned size)
> +{
> + DB_PRINT("CCAT_WRITE addr=0x%lx size=%u val=0x%lx\n", addr, size, val);
> +
> + BeckhoffCcat *s = opaque;
> +
> + if (addr > CCAT_MEM_SIZE - size) {
> + error_report("Overflow. Address or size is too large.\n");
> + exit(1);
> + }
> +
> + if (addr >= CCAT_EEPROM_OFFSET &&
> + addr <= CCAT_EEPROM_OFFSET + s->eeprom_size) {
> + beckhoff_ccat_eeprom_write(opaque, addr, val, size);
> + } else if (addr >= CCAT_DMA_OFFSET &&
> + addr <= CCAT_DMA_OFFSET + CCAT_DMA_SIZE) {
> + beckhoff_ccat_dma_write(opaque, addr, val, size);
> + } else {
> + memcpy(&s->mem[addr], &val, size);
> + }
> +}
> +
> +static const MemoryRegionOps beckhoff_ccat_ops = {
> + .read = beckhoff_ccat_read,
> + .write = beckhoff_ccat_write,
> + .endianness = DEVICE_LITTLE_ENDIAN,
> + .valid = {
> + .min_access_size = 1,
> + .max_access_size = 8,
> + },
> +};
> +
> +
> +static void beckhoff_ccat_reset(DeviceState *dev)
> +{
> + BeckhoffCcat *s = BECKHOFF_CCAT(dev);
> +
> + CcatFunctionBlock function_blocks[MAX_NUM_SLOTS] = {0};
> +
> + CcatFunctionBlock info_block = {
> + .type = CCAT_FUN_TYPE_ENTRY,
> + .revision = 0x0001,
> + .parameter = CCAT_INFO_BLOCK_PARAMS,
> + .address_offset = CCAT_ID_1,
> + .size = CCAT_ID_2
> + };
> + CcatFunctionBlock eeprom_block = {
> + .type = CCAT_FUN_TYPE_EEPROM,
> + .revision = 0x0001,
> + .parameter = 0,
> + .address_offset = CCAT_EEPROM_OFFSET,
> + .size = CCAT_EEPROM_SIZE
> + };
> + CcatFunctionBlock dma_block = {
> + .type = CCAT_FUN_TYPE_DMA,
> + .revision = 0x0000,
> + .parameter = 0,
> + .address_offset = CCAT_DMA_OFFSET,
> + .size = CCAT_DMA_SIZE
> + };
> +
> + /*
> + * The EEPROM interface is usually at function slot 11.
> + * The DMA controller is usually at function slot 15.
> + */
> + function_blocks[0] = info_block;
> + function_blocks[11] = eeprom_block;
> + function_blocks[15] = dma_block;
> +
> + memcpy(&s->mem[0], function_blocks, sizeof(function_blocks));
This isn't going to work on big-endian hosts, as noted above.
> +}
> +
> +static void beckhoff_ccat_realize(DeviceState *dev, Error **errp)
> +{
> + BeckhoffCcat *s = BECKHOFF_CCAT(dev);
> + BlockBackend *blk;
> +
> + blk = blk_by_name("ccat-eeprom");
No other device calls blk_by_name() to get its block backend:
you should not do so either. Use a qdev property and have
the SoC/board model set it.
> +
> + if (blk) {
> + uint64_t blk_size = blk_getlength(blk);
> + if (!is_power_of_2(blk_size)) {
> + error_report("Blockend size is not a power of two.");
In a realize function you have an Error** argument, so you
should report errors via error_setg() + return.
> + }
> +
> + if (blk_size < 512) {
> + error_report("Blockend size is too small. Using backup.");
> + s->eeprom_size = EEPROM_MEMORY_SIZE;
> + s->eeprom_storage = blk_blockalign(NULL, s->eeprom_size);
> + memset(s->eeprom_storage, 0x00, s->eeprom_size);
We don't try to fix things up like this in other block-backed
devices, I think, so we shouldn't do it here either.
> + } else {
> + DB_PRINT("EEPROM block backend found\n");
> + blk_set_perm(blk, BLK_PERM_WRITE, BLK_PERM_ALL, errp);
> +
> + s->eeprom_size = blk_size;
> + s->eeprom_blk = blk;
> + s->eeprom_storage = blk_blockalign(s->eeprom_blk, s->eeprom_size);
> +
> + if (!blk_check_size_and_read_all(s->eeprom_blk, DEVICE(s),
> + s->eeprom_storage, s->eeprom_size,
> + errp)) {
> + exit(1);
Don't silently exit(): use 'return', to report the error
to your caller.
> + }
> + }
> + } else {
> + s->eeprom_size = EEPROM_MEMORY_SIZE;
> + s->eeprom_storage = blk_blockalign(NULL, s->eeprom_size);
> + memset(s->eeprom_storage, 0x00, s->eeprom_size);
> + }
> +
> + beckhoff_ccat_reset(dev);
> +}
> +
> +static void beckhoff_ccat_init(Object *obj)
> +{
> + BeckhoffCcat *s = BECKHOFF_CCAT(obj);
> + SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
> +
> + memory_region_init_io(&s->iomem, obj, &beckhoff_ccat_ops, s,
> + TYPE_BECKHOFF_CCAT, CCAT_MEM_SIZE);
> + sysbus_init_mmio(sbd, &s->iomem);
> +}
> +
> +static void beckhoff_ccat_class_init(ObjectClass *klass, void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> + dc->realize = beckhoff_ccat_realize;
The guest can read and write mem[], so I think you need
a reset handler, and also a VMState for migration.
> +}
thanks
-- PMM
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2 01/14] hw/timer: Make frequency configurable
2025-08-15 9:00 ` [PATCH v2 01/14] hw/timer: Make frequency configurable Corvin Köhne
@ 2025-08-19 16:37 ` Peter Maydell
2025-08-19 16:41 ` Peter Maydell
1 sibling, 0 replies; 29+ messages in thread
From: Peter Maydell @ 2025-08-19 16:37 UTC (permalink / raw)
To: Corvin Köhne
Cc: qemu-devel, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block
On Fri, 15 Aug 2025 at 10:01, Corvin Köhne <corvin.koehne@gmail.com> wrote:
>
> From: YannickV <Y.Vossen@beckhoff.com>
>
> The a9 global timer and arm mp timers rely on the PERIPHCLK as
> their clock source. The current implementation does not take
> that into account. That causes problems for applications assuming
> other frequencies than 1 GHz.
>
> We can now configure frequencies for the a9 global timer and
> arm mp timer. By allowing these values to be set according to
> the application's needs, we ensure that the timers behave
> consistently with the expected system configuration.
>
> The frequency can also be set via the command line, for example
> for the a9 global timer:
> -global driver=arm.cortex-a9-global-timer,
> property=cpu-freq,value=1000000000
-global is a rare thing for special cases, you don't
need to mention it here. The standard case should be
"the SoC configures the device correctly".
> Information can be found in the Zynq 7000 SoC Technical
> Reference Manual under Timers.
> https://docs.amd.com/r/en-US/ug585-zynq-7000-SoC-TRM
>
> Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
> ---
> hw/timer/a9gtimer.c | 8 +++++---
> hw/timer/arm_mptimer.c | 15 +++++++++++----
> include/hw/timer/a9gtimer.h | 1 +
> include/hw/timer/arm_mptimer.h | 2 ++
> 4 files changed, 19 insertions(+), 7 deletions(-)
>
> diff --git a/hw/timer/a9gtimer.c b/hw/timer/a9gtimer.c
> index 9835c35483..a1f5540e75 100644
> --- a/hw/timer/a9gtimer.c
> +++ b/hw/timer/a9gtimer.c
> @@ -63,9 +63,9 @@ static inline int a9_gtimer_get_current_cpu(A9GTimerState *s)
> static inline uint64_t a9_gtimer_get_conv(A9GTimerState *s)
> {
> uint64_t prescale = extract32(s->control, R_CONTROL_PRESCALER_SHIFT,
> - R_CONTROL_PRESCALER_LEN);
> -
> - return (prescale + 1) * 10;
> + R_CONTROL_PRESCALER_LEN) + 1;
> + uint64_t ret = NANOSECONDS_PER_SECOND * prescale * 10;
> + return (uint32_t) (ret / s->cpu_clk_freq_hz);
Why the cast to uint32_t here ?
If you are doing an "x * NANOSECONDS_PER_SECOND / frequency"
calculation, use muldiv64(). (This does the calculation with a
96 bit intermediate result.)
> }
>
> static A9GTimerUpdate a9_gtimer_get_update(A9GTimerState *s)
> @@ -374,6 +374,8 @@ static const VMStateDescription vmstate_a9_gtimer = {
> };
>
> static const Property a9_gtimer_properties[] = {
> + DEFINE_PROP_UINT64("cpu-freq", A9GTimerState, cpu_clk_freq_hz,
> + NANOSECONDS_PER_SECOND),
You could have a comment mentioning that this default is 1GHz.
The modern way to do this is to have a Clock property, but
we have enough existing timer devices that take an integer
frequency that I don't object to another one.
> DEFINE_PROP_UINT32("num-cpu", A9GTimerState, num_cpu, 0),
> };
>
> diff --git a/hw/timer/arm_mptimer.c b/hw/timer/arm_mptimer.c
> index 803dad1e8a..a748b6ab1a 100644
> --- a/hw/timer/arm_mptimer.c
> +++ b/hw/timer/arm_mptimer.c
> @@ -59,9 +59,11 @@ static inline void timerblock_update_irq(TimerBlock *tb)
> }
>
> /* Return conversion factor from mpcore timer ticks to qemu timer ticks. */
> -static inline uint32_t timerblock_scale(uint32_t control)
> +static inline uint32_t timerblock_scale(TimerBlock *tb, uint32_t control)
> {
> - return (((control >> 8) & 0xff) + 1) * 10;
> + uint64_t prescale = (((control >> 8) & 0xff) + 1);
> + uint64_t ret = NANOSECONDS_PER_SECOND * prescale * 10;
> + return (uint32_t) (ret / tb->freq_hz);
> }
>
> /* Must be called within a ptimer transaction block */
> @@ -155,7 +157,7 @@ static void timerblock_write(void *opaque, hwaddr addr,
> ptimer_stop(tb->timer);
> }
> if ((control & 0xff00) != (value & 0xff00)) {
> - ptimer_set_period(tb->timer, timerblock_scale(value));
> + ptimer_set_period(tb->timer, timerblock_scale(tb, value));
> }
> if (value & 1) {
> uint64_t count = ptimer_get_count(tb->timer);
> @@ -222,7 +224,8 @@ static void timerblock_reset(TimerBlock *tb)
> ptimer_transaction_begin(tb->timer);
> ptimer_stop(tb->timer);
> ptimer_set_limit(tb->timer, 0, 1);
> - ptimer_set_period(tb->timer, timerblock_scale(0));
> + ptimer_set_period(tb->timer,
> + timerblock_scale(tb, tb->control));
> ptimer_transaction_commit(tb->timer);
> }
> }
> @@ -269,6 +272,7 @@ static void arm_mptimer_realize(DeviceState *dev, Error **errp)
> */
> for (i = 0; i < s->num_cpu; i++) {
> TimerBlock *tb = &s->timerblock[i];
> + tb->freq_hz = s->clk_freq_hz;
> tb->timer = ptimer_init(timerblock_tick, tb, PTIMER_POLICY);
> sysbus_init_irq(sbd, &tb->irq);
> memory_region_init_io(&tb->iomem, OBJECT(s), &timerblock_ops, tb,
> @@ -283,6 +287,7 @@ static const VMStateDescription vmstate_timerblock = {
> .minimum_version_id = 3,
> .fields = (const VMStateField[]) {
> VMSTATE_UINT32(control, TimerBlock),
> + VMSTATE_UINT64(freq_hz, TimerBlock),
> VMSTATE_UINT32(status, TimerBlock),
> VMSTATE_PTIMER(timer, TimerBlock),
> VMSTATE_END_OF_LIST()
You cannot add fields to a VMStateDescription without doing
something to handle migration compatibility. Fortunately,
in this case you don't need to add the field at all -- freq_hz
is a property value that cannot be updated by the guest at
runtime, so it doesn't need to be saved for migration.
> @@ -301,6 +306,8 @@ static const VMStateDescription vmstate_arm_mptimer = {
> };
>
> static const Property arm_mptimer_properties[] = {
> + DEFINE_PROP_UINT64("clk-freq", ARMMPTimerState, clk_freq_hz,
> + NANOSECONDS_PER_SECOND),
Can we be consistent about the property name and state struct
field that we use, please?
The one that seems to be most used in hw/timer/ is
a property name "clock-frequency" and a field name freq_hz.
thanks
-- PMM
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2 02/14] hw/timer: Make PERIPHCLK period configurable
2025-08-15 9:01 ` [PATCH v2 02/14] hw/timer: Make PERIPHCLK period configurable Corvin Köhne
@ 2025-08-19 16:38 ` Peter Maydell
0 siblings, 0 replies; 29+ messages in thread
From: Peter Maydell @ 2025-08-19 16:38 UTC (permalink / raw)
To: Corvin Köhne
Cc: qemu-devel, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block
On Fri, 15 Aug 2025 at 10:01, Corvin Köhne <corvin.koehne@gmail.com> wrote:
>
> From: YannickV <Y.Vossen@beckhoff.com>
>
> The a9 global timer and arm mp timer rely on the PERIPHCLK
> as their clock source. The period of PERIPHCLK (denoted as N)
> must be a multiple of the core CLK period, with N being equal
> to or greater than two. However, the current implementation
> does not take the PERIPHCLK period into account, leading to
> unexpected behavior in systems where the application assumes
> PERIPHCLK is clocked differently.
>
> The property periphclk-period represents the period N, the CLK
> is devided by to get the peripheral clock PERIPHCLK. We can now
> configure clock properties for the a9 global timer and arm mp
> timer. That ensures timers can behave according to the
> applications needs.
>
> The PERIPHCLK period can also be set via the command line, for
> example for the a9 global timer:
> -global driver=arm.cortex-a9-global-timer,
> property=periphclk-period,value=2
Why is the property for the main clock frequency a
value in Hz but the PERIPHCLK takes a period ?
thanks
-- PMM
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board
2025-08-15 18:06 ` [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Peter Maydell
@ 2025-08-19 16:40 ` Peter Maydell
2025-08-24 16:51 ` Edgar E. Iglesias
0 siblings, 1 reply; 29+ messages in thread
From: Peter Maydell @ 2025-08-19 16:40 UTC (permalink / raw)
To: Corvin Köhne
Cc: qemu-devel, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block
On Fri, 15 Aug 2025 at 19:06, Peter Maydell <peter.maydell@linaro.org> wrote:
>
> On Fri, 15 Aug 2025 at 10:01, Corvin Köhne <corvin.koehne@gmail.com> wrote:
> >
> > From: Corvin Köhne <c.koehne@beckhoff.com>
> >
> > Hi,
> >
> > Beckhoff has build a board, called CX7200, based on the Xilinx Zynq A9
> > platform. This commit series adds the Beckhoff CX7200 as new board variant to
> > QEMU.
> >
> > The emulation is able to successfully boot an CX7200 image. The image includes
> > some self tests executed on every boot. Only the cache self test fails due to
> > QEMU emulating the cache as always being coherent. The self tests include f.e.:
> >
> > * Network
> > * Flash
> > * CCAT DMA + EEPROM [1]
> > * TwinCAT (Beckhoff's automation control software [2])
> >
> > [1] https://github.com/beckhoff/ccat
> > [2] https://www.beckhoff.com/en-us/products/automation/
> >
> > YannickV (14):
> > hw/timer: Make frequency configurable
> > hw/timer: Make PERIPHCLK period configurable
> > hw/dma/zynq-devcfg: Handle bitstream loading via DMA to 0xffffffff
> > hw/arm/zynq-devcfg: Prevent unintended unlock during initialization
> > hw/dma/zynq: Ensure PCFG_DONE bit remains set to indicate PL is in
> > user mode
> > hw/dma/zynq-devcfg: Simulate dummy PL reset
> > hw/dma/zynq-devcfg: Indicate power-up status of PL
> > hw/dma/zynq-devcfg: Fix register memory
> > hw/misc: Add dummy ZYNQ DDR controller
> > hw/misc/zynq_slcr: Add logic for DCI configuration
> > hw/misc: Add Beckhoff CCAT device
> > hw/block/m25p80: Add HAS_SR_TB flag for is25lp016d
> > hw/arm: Add new machine based on xilinx-zynq-a9 for Beckhoff CX7200
> > docs/system/arm: Add support for Beckhoff CX7200
>
> This patchset is on my list to review. As an initial request,
> for a new board could we have a test in tests/functional/
> please?
I've also now reviewed the two initial generic-arm patches
and the ones where you add new device models. I had a
quick scan through the bug fix patches to the existing
zynq devices but I'd appreciate it if the Xilinx folks
could review those ones.
thanks
-- PMM
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2 01/14] hw/timer: Make frequency configurable
2025-08-15 9:00 ` [PATCH v2 01/14] hw/timer: Make frequency configurable Corvin Köhne
2025-08-19 16:37 ` Peter Maydell
@ 2025-08-19 16:41 ` Peter Maydell
1 sibling, 0 replies; 29+ messages in thread
From: Peter Maydell @ 2025-08-19 16:41 UTC (permalink / raw)
To: Corvin Köhne
Cc: qemu-devel, Corvin Köhne, qemu-arm, Kevin Wolf,
Paolo Bonzini, Edgar E. Iglesias, Alistair Francis,
Yannick Voßen, Hanna Reitz, qemu-block
On Fri, 15 Aug 2025 at 10:01, Corvin Köhne <corvin.koehne@gmail.com> wrote:
>
> From: YannickV <Y.Vossen@beckhoff.com>
> Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
Is it intentional that the names in the From and
the Signed-off-by: are different ?
thanks
-- PMM
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2 04/14] hw/arm/zynq-devcfg: Prevent unintended unlock during initialization
2025-08-15 9:01 ` [PATCH v2 04/14] hw/arm/zynq-devcfg: Prevent unintended unlock during initialization Corvin Köhne
@ 2025-08-24 15:47 ` Edgar E. Iglesias
0 siblings, 0 replies; 29+ messages in thread
From: Edgar E. Iglesias @ 2025-08-24 15:47 UTC (permalink / raw)
To: Corvin Köhne
Cc: qemu-devel, Peter Maydell, Corvin Köhne, qemu-arm,
Kevin Wolf, Paolo Bonzini, Alistair Francis, Yannick Voßen,
Hanna Reitz, qemu-block
On Fri, Aug 15, 2025 at 11:01:02AM +0200, Corvin Köhne wrote:
> From: YannickV <Y.Vossen@beckhoff.com>
>
> During the emulation startup, all registers are reset, which triggers the
> `r_unlock_post_write` function with a value of 0. This led to an
> unintended memory access disable, making the devcfg unusable.
>
> During startup, the memory space no longer gets locked.
>
> Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
Reviewed-by: Edgar E. Iglesias <edgar.iglesias@amd.com>
> ---
> hw/dma/xlnx-zynq-devcfg.c | 4 +++-
> 1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/hw/dma/xlnx-zynq-devcfg.c b/hw/dma/xlnx-zynq-devcfg.c
> index b838c1c0d0..f28d0015e6 100644
> --- a/hw/dma/xlnx-zynq-devcfg.c
> +++ b/hw/dma/xlnx-zynq-devcfg.c
> @@ -221,7 +221,9 @@ static void r_unlock_post_write(RegisterInfo *reg, uint64_t val)
> {
> XlnxZynqDevcfg *s = XLNX_ZYNQ_DEVCFG(reg->opaque);
> const char *device_prefix = object_get_typename(OBJECT(s));
> -
> + if (device_is_in_reset(DEVICE(s))) {
> + return;
> + }
> if (val == R_UNLOCK_MAGIC) {
> DB_PRINT("successful unlock\n");
> s->regs[R_CTRL] |= R_CTRL_PCAP_PR_MASK;
> --
> 2.50.1
>
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2 06/14] hw/dma/zynq-devcfg: Simulate dummy PL reset
2025-08-15 9:01 ` [PATCH v2 06/14] hw/dma/zynq-devcfg: Simulate dummy PL reset Corvin Köhne
@ 2025-08-24 15:53 ` Edgar E. Iglesias
0 siblings, 0 replies; 29+ messages in thread
From: Edgar E. Iglesias @ 2025-08-24 15:53 UTC (permalink / raw)
To: Corvin Köhne
Cc: qemu-devel, Peter Maydell, Corvin Köhne, qemu-arm,
Kevin Wolf, Paolo Bonzini, Alistair Francis, Yannick Voßen,
Hanna Reitz, qemu-block
On Fri, Aug 15, 2025 at 11:01:04AM +0200, Corvin Köhne wrote:
> From: YannickV <Y.Vossen@beckhoff.com>
>
> Setting PCFG_PROG_B should reset the PL. After a reset PCFG_INIT
> should indicate that the reset is finished successfully.
>
> In order to add a MMIO-Device as part of the PL in the Zynq, the
> reset logic must succeed. The PCFG_INIT flag is now set when the
> PL reset is triggered by PCFG_PROG_B. Indicating the reset was
> successful.
>
> Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
Reviewed-by: Edgar E. Iglesias <edgar.iglesias@amd.com>
> ---
> hw/dma/xlnx-zynq-devcfg.c | 9 +++++++++
> 1 file changed, 9 insertions(+)
>
> diff --git a/hw/dma/xlnx-zynq-devcfg.c b/hw/dma/xlnx-zynq-devcfg.c
> index 60ea351494..c699df6ad4 100644
> --- a/hw/dma/xlnx-zynq-devcfg.c
> +++ b/hw/dma/xlnx-zynq-devcfg.c
> @@ -49,6 +49,7 @@
>
> REG32(CTRL, 0x00)
> FIELD(CTRL, FORCE_RST, 31, 1) /* Not supported, wr ignored */
> + FIELD(CTRL, PCFG_PROG_B, 30, 1)
> FIELD(CTRL, PCAP_PR, 27, 1) /* Forced to 0 on bad unlock */
> FIELD(CTRL, PCAP_MODE, 26, 1)
> FIELD(CTRL, MULTIBOOT_EN, 24, 1)
> @@ -116,6 +117,7 @@ REG32(STATUS, 0x14)
> FIELD(STATUS, PSS_GTS_USR_B, 11, 1)
> FIELD(STATUS, PSS_FST_CFG_B, 10, 1)
> FIELD(STATUS, PSS_CFG_RESET_B, 5, 1)
> + FIELD(STATUS, PCFG_INIT, 4, 1)
>
> REG32(DMA_SRC_ADDR, 0x18)
> REG32(DMA_DST_ADDR, 0x1C)
> @@ -204,6 +206,13 @@ static uint64_t r_ctrl_pre_write(RegisterInfo *reg, uint64_t val)
> val |= lock_ctrl_map[i] & s->regs[R_CTRL];
> }
> }
> +
> + if (FIELD_EX32(val, CTRL, PCFG_PROG_B)) {
> + s->regs[R_STATUS] |= R_STATUS_PCFG_INIT_MASK;
> + } else {
> + s->regs[R_STATUS] &= ~R_STATUS_PCFG_INIT_MASK;
> + }
> +
> return val;
> }
>
> --
> 2.50.1
>
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2 05/14] hw/dma/zynq: Ensure PCFG_DONE bit remains set to indicate PL is in user mode
2025-08-15 9:01 ` [PATCH v2 05/14] hw/dma/zynq: Ensure PCFG_DONE bit remains set to indicate PL is in user mode Corvin Köhne
@ 2025-08-24 16:09 ` Edgar E. Iglesias
0 siblings, 0 replies; 29+ messages in thread
From: Edgar E. Iglesias @ 2025-08-24 16:09 UTC (permalink / raw)
To: Corvin Köhne
Cc: qemu-devel, Peter Maydell, Corvin Köhne, qemu-arm,
Kevin Wolf, Paolo Bonzini, Alistair Francis, Yannick Voßen,
Hanna Reitz, qemu-block
On Fri, Aug 15, 2025 at 11:01:03AM +0200, Corvin Köhne wrote:
> From: YannickV <Y.Vossen@beckhoff.com>
>
> All register bits are clear on write by writing 1s to those bits, however
> the register bits will only be cleared if the condition that sets the
> interrupt flag is no longer true. Since we can assume that programming
> is always done, the `PCFG_DONE` flag is always set to 1, so it will not
> never be cleared.
>
> Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
> ---
> hw/dma/xlnx-zynq-devcfg.c | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/hw/dma/xlnx-zynq-devcfg.c b/hw/dma/xlnx-zynq-devcfg.c
> index f28d0015e6..60ea351494 100644
> --- a/hw/dma/xlnx-zynq-devcfg.c
> +++ b/hw/dma/xlnx-zynq-devcfg.c
> @@ -188,6 +188,8 @@ static void r_ixr_post_write(RegisterInfo *reg, uint64_t val)
> {
> XlnxZynqDevcfg *s = XLNX_ZYNQ_DEVCFG(reg->opaque);
>
> + s->regs[R_INT_STS] |= R_INT_STS_PCFG_DONE_MASK;
> +
Looks like you've got some stray spaces in the empty line.
I'm fine with this but another way to handle PCFG_DONE could be to have
some state that goes true after the first programming. e.g:
s->regs[R_INT_STS] |= s->pcfg_done ? R_INT_STS_PCFG_DONE_MASK : 0;
On the other hand, for direct Linux boots we may want this to be always
one...
Anyway, with the whitespace fixes:
Reviewed-by: Edgar E. Iglesias <edgar.iglesias@amd.com>
> xlnx_zynq_devcfg_update_ixr(s);
> }
>
> --
> 2.50.1
>
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2 07/14] hw/dma/zynq-devcfg: Indicate power-up status of PL
2025-08-15 9:01 ` [PATCH v2 07/14] hw/dma/zynq-devcfg: Indicate power-up status of PL Corvin Köhne
@ 2025-08-24 16:11 ` Edgar E. Iglesias
0 siblings, 0 replies; 29+ messages in thread
From: Edgar E. Iglesias @ 2025-08-24 16:11 UTC (permalink / raw)
To: Corvin Köhne
Cc: qemu-devel, Peter Maydell, Corvin Köhne, qemu-arm,
Kevin Wolf, Paolo Bonzini, Alistair Francis, Yannick Voßen,
Hanna Reitz, qemu-block
On Fri, Aug 15, 2025 at 11:01:05AM +0200, Corvin Köhne wrote:
> From: YannickV <Y.Vossen@beckhoff.com>
>
> It is assumed, that the programmable logic (PL) is always powered
> during emulation. Therefor the PCFG_POR_B bit in the MCTRL register
> is set.
>
> Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
Reviewed-by: Edgar E. Iglesias <edgar.iglesias@amd.com>
> ---
> hw/dma/xlnx-zynq-devcfg.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/hw/dma/xlnx-zynq-devcfg.c b/hw/dma/xlnx-zynq-devcfg.c
> index c699df6ad4..064955a0f8 100644
> --- a/hw/dma/xlnx-zynq-devcfg.c
> +++ b/hw/dma/xlnx-zynq-devcfg.c
> @@ -333,7 +333,8 @@ static const RegisterAccessInfo xlnx_zynq_devcfg_regs_info[] = {
> /* Silicon 3.0 for version field, the mysterious reserved bit 23
> * and QEMU platform identifier.
> */
> - .reset = 0x2 << R_MCTRL_PS_VERSION_SHIFT | 1 << 23 | R_MCTRL_QEMU_MASK,
> + .reset = 0x2 << R_MCTRL_PS_VERSION_SHIFT | 1 << 23 |
> + R_MCTRL_PCFG_POR_B_MASK | R_MCTRL_QEMU_MASK,
> .ro = ~R_MCTRL_INT_PCAP_LPBK_MASK,
> .rsvd = 0x00f00303,
> },
> --
> 2.50.1
>
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2 09/14] hw/misc: Add dummy ZYNQ DDR controller
2025-08-19 15:43 ` Peter Maydell
@ 2025-08-24 16:24 ` Edgar E. Iglesias
0 siblings, 0 replies; 29+ messages in thread
From: Edgar E. Iglesias @ 2025-08-24 16:24 UTC (permalink / raw)
To: Peter Maydell
Cc: Corvin Köhne, qemu-devel, Corvin Köhne, qemu-arm,
Kevin Wolf, Paolo Bonzini, Alistair Francis, Yannick Voßen,
Hanna Reitz, qemu-block
[-- Attachment #1: Type: text/plain, Size: 2914 bytes --]
On Tue, Aug 19, 2025 at 5:44 PM Peter Maydell <peter.maydell@linaro.org>
wrote:
> On Fri, 15 Aug 2025 at 10:01, Corvin Köhne <corvin.koehne@gmail.com>
> wrote:
> >
> > From: YannickV <Y.Vossen@beckhoff.com>
> >
> > A dummy DDR controller for ZYNQ has been added. While all registers are
> present,
> > not all are functional. Read and write access is validated, and the user
> mode
> > can be set. This provides a basic DDR controller initialization,
> preventing
> > system hangs due to endless polling or similar issues.
> >
> > Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
> > ---
> > hw/misc/Kconfig | 3 +
> > hw/misc/meson.build | 1 +
> > hw/misc/xlnx-zynq-ddrc.c | 393 +++++++++++++++++++++++++++++++
> > include/hw/misc/xlnx-zynq-ddrc.h | 140 +++++++++++
> > 4 files changed, 537 insertions(+)
> > create mode 100644 hw/misc/xlnx-zynq-ddrc.c
> > create mode 100644 include/hw/misc/xlnx-zynq-ddrc.h
> >
> > diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
> > index 8f9ce2f68c..99548e146f 100644
> > --- a/hw/misc/Kconfig
> > +++ b/hw/misc/Kconfig
> > @@ -220,4 +220,7 @@ config IOSB
> > config XLNX_VERSAL_TRNG
> > bool
> >
> > +config XLNX_ZYNQ_DDRC
> > + bool
> > +
> > source macio/Kconfig
> > diff --git a/hw/misc/meson.build b/hw/misc/meson.build
> > index 55f493521b..6ee7b6c71d 100644
> > --- a/hw/misc/meson.build
> > +++ b/hw/misc/meson.build
> > @@ -89,6 +89,7 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
> > ))
> > system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
> > system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
> > +system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('xlnx-zynq-ddrc.c'))
> > system_ss.add(when: 'CONFIG_XLNX_ZYNQMP_ARM', if_true:
> files('xlnx-zynqmp-crf.c'))
> > system_ss.add(when: 'CONFIG_XLNX_ZYNQMP_ARM', if_true:
> files('xlnx-zynqmp-apu-ctrl.c'))
> > system_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files(
> > diff --git a/hw/misc/xlnx-zynq-ddrc.c b/hw/misc/xlnx-zynq-ddrc.c
> > new file mode 100644
> > index 0000000000..8151a0e3ee
> > --- /dev/null
> > +++ b/hw/misc/xlnx-zynq-ddrc.c
>
> All new files must start with a comment which at least
> (a) says what the file is for and (b) has the SPDX
> license indicator. You can optionally also add an authorship
> or copyright line here if you/your employer need that.
>
> Same applies to the .h file.
>
> > +static void zynq_ddrctrl_class_init(ObjectClass *klass, void *data)
> > +{
> > + DeviceClass *dc = DEVICE_CLASS(klass);
> > +
> > + device_class_set_legacy_reset(dc, zynq_ddrctrl_reset);
> > +}
>
> This looks like it's missing registration of a vmstate
> struct for state save/load.
>
> thanks
> -- PMM
>
Except for the stuff that Peter mentions, the rest looks good to me!
Cheers,
Edgar
[-- Attachment #2: Type: text/html, Size: 3946 bytes --]
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2 10/14] hw/misc/zynq_slcr: Add logic for DCI configuration
2025-08-15 9:01 ` [PATCH v2 10/14] hw/misc/zynq_slcr: Add logic for DCI configuration Corvin Köhne
@ 2025-08-24 16:41 ` Edgar E. Iglesias
0 siblings, 0 replies; 29+ messages in thread
From: Edgar E. Iglesias @ 2025-08-24 16:41 UTC (permalink / raw)
To: Corvin Köhne
Cc: qemu-devel, Peter Maydell, Corvin Köhne, qemu-arm,
Kevin Wolf, Paolo Bonzini, Alistair Francis, Yannick Voßen,
Hanna Reitz, qemu-block, Edgar E. Iglesias
On Fri, Aug 15, 2025 at 11:01:08AM +0200, Corvin Köhne wrote:
> From: YannickV <Y.Vossen@beckhoff.com>
>
> The registers for the digitally controlled impedance (DCI) clock are
> part of the system level control registers (SLCR). The DONE bit in
> the status register indicates a successfull DCI calibration. An
> description of the calibration process can be found here:
> https://docs.amd.com/r/en-US/ug585-zynq-7000-SoC-TRM/DDR-IOB-Impedance-Calibration
>
> The DCI control register and status register have been added. As soon
> as the ENABLE and RESET bit are set, the RESET bit has also been toggled
> to 0 before and the UPDATE_CONTROL is not set, the DONE bit in the status
> register is set. If these bits change the DONE bit is reset. Note that the
> option bits are not taken into consideration.
>
> Signed-off-by: Yannick Voßen <y.vossen@beckhoff.com>
> Reviewed-by: Edgar E. Iglesias <edgar.iglesias@amd.com>
BTW, I just noticed that this patch has style problems:
e8874ea66c (HEAD, beckhoff) hw/misc/zynq_slcr: Add logic for DCI configuration
ERROR: trailing whitespace
#72: FILE: hw/misc/zynq_slcr.c:571:
+ if (!FIELD_EX32(val, DDRIOB_DCI_CTRL, RESET) && $
total: 1 errors, 0 warnings, 61 lines checked
> ---
> hw/misc/zynq_slcr.c | 31 +++++++++++++++++++++++++++++++
> 1 file changed, 31 insertions(+)
>
> diff --git a/hw/misc/zynq_slcr.c b/hw/misc/zynq_slcr.c
> index a766bab182..8d15f0cc66 100644
> --- a/hw/misc/zynq_slcr.c
> +++ b/hw/misc/zynq_slcr.c
> @@ -180,6 +180,12 @@ REG32(GPIOB_CFG_HSTL, 0xb14)
> REG32(GPIOB_DRVR_BIAS_CTRL, 0xb18)
>
> REG32(DDRIOB, 0xb40)
> +REG32(DDRIOB_DCI_CTRL, 0xb70)
> + FIELD(DDRIOB_DCI_CTRL, RESET, 0, 1)
> + FIELD(DDRIOB_DCI_CTRL, ENABLE, 1, 1)
> + FIELD(DDRIOB_DCI_CTRL, UPDATE_CONTROL, 20, 1)
> +REG32(DDRIOB_DCI_STATUS, 0xb74)
> + FIELD(DDRIOB_DCI_STATUS, DONE, 13, 1)
> #define DDRIOB_LENGTH 14
>
> #define ZYNQ_SLCR_MMIO_SIZE 0x1000
> @@ -193,6 +199,8 @@ struct ZynqSLCRState {
>
> MemoryRegion iomem;
>
> + bool ddriob_dci_ctrl_reset_toggled;
> +
> uint32_t regs[ZYNQ_SLCR_NUM_REGS];
>
> Clock *ps_clk;
> @@ -331,6 +339,8 @@ static void zynq_slcr_reset_init(Object *obj, ResetType type)
>
> DB_PRINT("RESET\n");
>
> + s->ddriob_dci_ctrl_reset_toggled = false;
> +
> s->regs[R_LOCKSTA] = 1;
> /* 0x100 - 0x11C */
> s->regs[R_ARM_PLL_CTRL] = 0x0001A008;
> @@ -418,6 +428,8 @@ static void zynq_slcr_reset_init(Object *obj, ResetType type)
> s->regs[R_DDRIOB + 4] = s->regs[R_DDRIOB + 5] = s->regs[R_DDRIOB + 6]
> = 0x00000e00;
> s->regs[R_DDRIOB + 12] = 0x00000021;
> +
> + s->regs[R_DDRIOB_DCI_CTRL] = 0x00000020;
> }
>
> static void zynq_slcr_reset_hold(Object *obj, ResetType type)
> @@ -554,6 +566,25 @@ static void zynq_slcr_write(void *opaque, hwaddr offset,
> (int)offset, (unsigned)val & 0xFFFF);
> }
> return;
> +
> + case R_DDRIOB_DCI_CTRL:
> + if (!FIELD_EX32(val, DDRIOB_DCI_CTRL, RESET) &&
> + FIELD_EX32(s->regs[R_DDRIOB_DCI_CTRL], DDRIOB_DCI_CTRL, RESET)) {
> +
> + s->ddriob_dci_ctrl_reset_toggled = true;
> + DB_PRINT("DDRIOB DCI CTRL RESET was toggled\n");
> + }
> +
> + if (FIELD_EX32(val, DDRIOB_DCI_CTRL, ENABLE) &&
> + FIELD_EX32(val, DDRIOB_DCI_CTRL, RESET) &&
> + !FIELD_EX32(val, DDRIOB_DCI_CTRL, UPDATE_CONTROL) &&
> + s->ddriob_dci_ctrl_reset_toggled) {
> +
> + s->regs[R_DDRIOB_DCI_STATUS] |= R_DDRIOB_DCI_STATUS_DONE_MASK;
> + } else {
> + s->regs[R_DDRIOB_DCI_STATUS] &= ~R_DDRIOB_DCI_STATUS_DONE_MASK;
> + }
> + break;
> }
>
> if (s->regs[R_LOCKSTA]) {
> --
> 2.50.1
>
^ permalink raw reply [flat|nested] 29+ messages in thread
* Re: [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board
2025-08-19 16:40 ` Peter Maydell
@ 2025-08-24 16:51 ` Edgar E. Iglesias
0 siblings, 0 replies; 29+ messages in thread
From: Edgar E. Iglesias @ 2025-08-24 16:51 UTC (permalink / raw)
To: Peter Maydell
Cc: Corvin Köhne, qemu-devel, Corvin Köhne, qemu-arm,
Kevin Wolf, Paolo Bonzini, Alistair Francis, Yannick Voßen,
Hanna Reitz, qemu-block
On Tue, Aug 19, 2025 at 05:40:14PM +0100, Peter Maydell wrote:
> On Fri, 15 Aug 2025 at 19:06, Peter Maydell <peter.maydell@linaro.org> wrote:
> >
> > On Fri, 15 Aug 2025 at 10:01, Corvin Köhne <corvin.koehne@gmail.com> wrote:
> > >
> > > From: Corvin Köhne <c.koehne@beckhoff.com>
> > >
> > > Hi,
> > >
> > > Beckhoff has build a board, called CX7200, based on the Xilinx Zynq A9
> > > platform. This commit series adds the Beckhoff CX7200 as new board variant to
> > > QEMU.
> > >
> > > The emulation is able to successfully boot an CX7200 image. The image includes
> > > some self tests executed on every boot. Only the cache self test fails due to
> > > QEMU emulating the cache as always being coherent. The self tests include f.e.:
> > >
> > > * Network
> > > * Flash
> > > * CCAT DMA + EEPROM [1]
> > > * TwinCAT (Beckhoff's automation control software [2])
> > >
> > > [1] https://github.com/beckhoff/ccat
> > > [2] https://www.beckhoff.com/en-us/products/automation/
> > >
> > > YannickV (14):
> > > hw/timer: Make frequency configurable
> > > hw/timer: Make PERIPHCLK period configurable
> > > hw/dma/zynq-devcfg: Handle bitstream loading via DMA to 0xffffffff
> > > hw/arm/zynq-devcfg: Prevent unintended unlock during initialization
> > > hw/dma/zynq: Ensure PCFG_DONE bit remains set to indicate PL is in
> > > user mode
> > > hw/dma/zynq-devcfg: Simulate dummy PL reset
> > > hw/dma/zynq-devcfg: Indicate power-up status of PL
> > > hw/dma/zynq-devcfg: Fix register memory
> > > hw/misc: Add dummy ZYNQ DDR controller
> > > hw/misc/zynq_slcr: Add logic for DCI configuration
> > > hw/misc: Add Beckhoff CCAT device
> > > hw/block/m25p80: Add HAS_SR_TB flag for is25lp016d
> > > hw/arm: Add new machine based on xilinx-zynq-a9 for Beckhoff CX7200
> > > docs/system/arm: Add support for Beckhoff CX7200
> >
> > This patchset is on my list to review. As an initial request,
> > for a new board could we have a test in tests/functional/
> > please?
>
> I've also now reviewed the two initial generic-arm patches
> and the ones where you add new device models. I had a
> quick scan through the bug fix patches to the existing
> zynq devices but I'd appreciate it if the Xilinx folks
> could review those ones.
>
Thanks Peter,
I reviewed patches 3 - 8 and 10.
Patch 5 and 10 have some minor whitespace issues, perhaps we could fix
on commit...
f5badc6fe5 (HEAD) hw/dma/zynq: Ensure PCFG_DONE bit remains set to indicate PL is in user mode
3: ../check.sh
ERROR: trailing whitespace
#24: FILE: hw/dma/xlnx-zynq-devcfg.c:192:
+ $
total: 1 errors, 0 warnings, 8 lines checked
e8874ea66c (HEAD, beckhoff) hw/misc/zynq_slcr: Add logic for DCI configuration
7: ../check.sh
ERROR: trailing whitespace
#72: FILE: hw/misc/zynq_slcr.c:571:
+ if (!FIELD_EX32(val, DDRIOB_DCI_CTRL, RESET) && $
total: 1 errors, 0 warnings, 61 lines checked
Cheers,
Edgar
^ permalink raw reply [flat|nested] 29+ messages in thread
end of thread, other threads:[~2025-08-24 16:52 UTC | newest]
Thread overview: 29+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-15 9:00 [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Corvin Köhne
2025-08-15 9:00 ` [PATCH v2 01/14] hw/timer: Make frequency configurable Corvin Köhne
2025-08-19 16:37 ` Peter Maydell
2025-08-19 16:41 ` Peter Maydell
2025-08-15 9:01 ` [PATCH v2 02/14] hw/timer: Make PERIPHCLK period configurable Corvin Köhne
2025-08-19 16:38 ` Peter Maydell
2025-08-15 9:01 ` [PATCH v2 03/14] hw/dma/zynq-devcfg: Handle bitstream loading via DMA to 0xffffffff Corvin Köhne
2025-08-15 9:01 ` [PATCH v2 04/14] hw/arm/zynq-devcfg: Prevent unintended unlock during initialization Corvin Köhne
2025-08-24 15:47 ` Edgar E. Iglesias
2025-08-15 9:01 ` [PATCH v2 05/14] hw/dma/zynq: Ensure PCFG_DONE bit remains set to indicate PL is in user mode Corvin Köhne
2025-08-24 16:09 ` Edgar E. Iglesias
2025-08-15 9:01 ` [PATCH v2 06/14] hw/dma/zynq-devcfg: Simulate dummy PL reset Corvin Köhne
2025-08-24 15:53 ` Edgar E. Iglesias
2025-08-15 9:01 ` [PATCH v2 07/14] hw/dma/zynq-devcfg: Indicate power-up status of PL Corvin Köhne
2025-08-24 16:11 ` Edgar E. Iglesias
2025-08-15 9:01 ` [PATCH v2 08/14] hw/dma/zynq-devcfg: Fix register memory Corvin Köhne
2025-08-15 9:01 ` [PATCH v2 09/14] hw/misc: Add dummy ZYNQ DDR controller Corvin Köhne
2025-08-19 15:43 ` Peter Maydell
2025-08-24 16:24 ` Edgar E. Iglesias
2025-08-15 9:01 ` [PATCH v2 10/14] hw/misc/zynq_slcr: Add logic for DCI configuration Corvin Köhne
2025-08-24 16:41 ` Edgar E. Iglesias
2025-08-15 9:01 ` [PATCH v2 11/14] hw/misc: Add Beckhoff CCAT device Corvin Köhne
2025-08-19 16:03 ` Peter Maydell
2025-08-15 9:01 ` [PATCH v2 12/14] hw/block/m25p80: Add HAS_SR_TB flag for is25lp016d Corvin Köhne
2025-08-15 9:01 ` [PATCH v2 13/14] hw/arm: Add new machine based on xilinx-zynq-a9 for Beckhoff CX7200 Corvin Köhne
2025-08-15 9:01 ` [PATCH v2 14/14] docs/system/arm: Add support " Corvin Köhne
2025-08-15 18:06 ` [PATCH v2 00/14] hw/arm: add Beckhoff CX7200 board Peter Maydell
2025-08-19 16:40 ` Peter Maydell
2025-08-24 16:51 ` Edgar E. Iglesias
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).