diff --git a/MAINTAINERS b/MAINTAINERS index 912900615ef39..c4e25ea9412c9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -882,6 +882,22 @@ F: include/hw/misc/arm_integrator_debug.h F: tests/functional/arm/test_integratorcp.py F: docs/system/arm/integratorcp.rst +M5Stack AI Pyramid (AX650X) +M: Chao Liu +L: qemu-arm@nongnu.org +S: Maintained +F: docs/system/arm/ax650x-pyramid.rst +F: hw/arm/ax650x*.c +F: hw/gpio/dw-apb-gpio.c +F: hw/misc/ax650x-*.c +F: hw/sd/ax650x-sdhci.c +F: include/hw/arm/ax650x*.h +F: include/hw/gpio/dw-apb-gpio.h +F: include/hw/misc/ax650x-*.h +F: include/hw/sd/ax650x-sdhci.h +F: tests/functional/aarch64/test_ax650x_ubuntu.py +F: tests/qtest/ax650x-pyramid-test.c + MAX78000FTHR L: qemu-arm@nongnu.org S: Orphan diff --git a/README.md b/README.md index 20883ac8ea650..75b384e57113d 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ for new machine requests or bug reports. | RISC-V | [`k3-pico-itx`](docs/system/riscv/spacemit-k3.rst) | ✅ | ✅ | PM | | RISC-V | [`milkv-duo`](docs/system/riscv/milkv-duo.rst) | ✅ | ✅ | UP | | RISC-V | [`riscv-server-ref`](docs/system/riscv/riscv-server-ref.rst) | ✅ | ✅ | UP | +| ARM | [`ax650x-pyramid`](docs/system/arm/ax650x-pyramid.rst) | ✅ | ❌ | PM | | ARM | [`phytium-pi`](docs/system/arm/phytium-pi.rst) | ✅ | ✅ | PM | | ARM | [`rk3588-evb`](docs/system/arm/rk3588.rst) | ✅ | ✅ | PM | | ARM | [`rk3588s-roc-pc`](docs/system/arm/rk3588.rst) | ✅ | ✅ | PM | @@ -34,3 +35,77 @@ It provides agent skills for planning, register extraction, peripheral modeling, board modeling, qtest, build, debugging, and verification. - Skill repository: + +## AX650X Pyramid quick start + +The `ax650x-pyramid` machine directly boots Linux on the M5Stack AI Pyramid / +AXERA AX650X platform. Detailed machine documentation is available in +[docs/system/arm/ax650x-pyramid.rst](docs/system/arm/ax650x-pyramid.rst). + +### Boot Ubuntu 22.04 from eMMC + +The Ubuntu image has no MBR or GPT. Linux creates its twelve partitions from +the fixed `blkdevparts` command line. The command below uses `snapshot=on`, so +guest writes are discarded when QEMU exits. + +```sh +QEMU=${QEMU:-build/qemu-system-aarch64} +ASSET_DIR=${ASSET_DIR:-assets/ax650x} +KERNEL=${KERNEL:-$ASSET_DIR/Image-5.15.73-axera} +EMMC=${EMMC:-$ASSET_DIR/ax650x-ubuntu-22.04-emmc.raw} + +PARTS='mmcblk0:1536K(uboot),1536K(uboot_bk),1M(env),20M(param)' +PARTS="$PARTS,6M(logo),1M(dtb),64M(kernel),1M(atf),1M(optee)" +PARTS="$PARTS,1M(recovery_dtb),74M(recovery),30380032K(rootfs)" +CMDLINE='console=ttyS0,115200n8 earlycon=uart8250,mmio32,0x2016000' +CMDLINE="$CMDLINE root=/dev/mmcblk0p12 rootfstype=ext4 rw rootwait" +CMDLINE="$CMDLINE blkdevparts=$PARTS" +CMDLINE="$CMDLINE systemd.show_status=yes systemd.log_target=console" + +for input in "$QEMU" "$KERNEL" "$EMMC"; do + if [ ! -r "$input" ]; then + echo "missing input: $input" >&2 + exit 1 + fi +done + +exec "$QEMU" \ + -machine ax650x-pyramid \ + -accel tcg,thread=multi \ + -cpu cortex-a55 \ + -smp 8 \ + -m 2G \ + -kernel "$KERNEL" \ + -append "$CMDLINE" \ + -drive "file=$EMMC,if=sd,format=raw,snapshot=on" \ + -chardev stdio,id=serial0,signal=off \ + -serial chardev:serial0 \ + -display none \ + -monitor none \ + -no-reboot +``` + +Override `QEMU`, `KERNEL`, or `EMMC` in the environment when the artifacts +live elsewhere. Remove `snapshot=on` only when persistent image writes are +intentional. + +### Run the Ubuntu quick-boot functional test + +The functional test uses the same direct-boot contract but lets the test +harness own the serial chardev. It is in the `thorough` suite because the +kernel and compressed eMMC image are downloaded assets. + +```sh +meson test -C build \ + --suite thorough \ + func-aarch64-ax650x_ubuntu \ + --print-errorlogs +``` + +The pinned kernel and qcow2 image are published at: + + + +The harness verifies both SHA-256 digests. The test uses disposable eMMC +writes and waits for DWMAC probe, partition 12, the mounted ext4 root +filesystem, Ubuntu readiness markers, and the serial login prompt. diff --git a/docs/system/arm/ax650x-pyramid.rst b/docs/system/arm/ax650x-pyramid.rst new file mode 100644 index 0000000000000..a7cb9662bdf42 --- /dev/null +++ b/docs/system/arm/ax650x-pyramid.rst @@ -0,0 +1,142 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +M5Stack AI Pyramid (``ax650x-pyramid``) +======================================== + +Overview +-------- + +The ``ax650x-pyramid`` machine models the Linux boot subset of the M5Stack +AI Pyramid, whose runtime device tree identifies the SoC as +``axera,ax650x``. The model is intended for direct kernel boot with an +AX650X-enabled Arm64 Linux kernel. It does not model the product's AXERA +accelerators or firmware boot chain. + +The machine has a fixed topology matching the target system: eight Cortex-A55 +CPUs and 2 GiB of RAM starting at physical address ``0x100000000``. Other CPU +counts and memory sizes are rejected. + +Supported devices +----------------- + +The machine provides the following devices and architectural services: + +* eight Cortex-A55 CPUs, with MPIDRs ``0x000`` through ``0x700``; +* GIC-400 compatible GICv2 interrupt controller with virtualization + extensions; +* Arm generic timers running at 24 MHz; +* PSCI 1.0 using the SMC conduit; +* the AXERA UART0 console at ``0x02016000``, including the extension + registers needed by the AXERA 8250 driver; +* an AX650X SDHCI/eMMC controller at ``0x28000000``, including the vendor PHY + and eMMC registers used during Linux probe; +* two Synopsys DWMAC 4.10a compatible Ethernet controllers, including the + AX650X clock/reset glue, MDIO PHY identity, DMA and interrupt paths; +* the DesignWare APB GPIO blocks used for PHY reset; and +* the AX650X hardware spinlock block used by the GPIO driver. + +QEMU generates a minimal device tree containing only those implemented +devices. An eMMC backend supplied with ``if=sd`` is attached to the AX650X +controller. + +Direct Linux boot +----------------- + +The model requires an uncompressed Arm64 ``Image`` containing the AXERA UART +and SDHCI drivers. The following example boots an ext4 root filesystem from +partition 12 of an eMMC image that uses the target's fixed partition layout. +The image has no MBR or GPT, so the complete ``blkdevparts`` argument is +required: + +.. code-block:: shell + + QEMU=${QEMU:-build/qemu-system-aarch64} + ASSET_DIR=${ASSET_DIR:-assets/ax650x} + KERNEL=${KERNEL:-$ASSET_DIR/Image-5.15.73-axera} + EMMC=${EMMC:-$ASSET_DIR/ax650x-ubuntu-22.04-emmc.raw} + + PARTS='mmcblk0:1536K(uboot),1536K(uboot_bk),1M(env),20M(param)' + PARTS="$PARTS,6M(logo),1M(dtb),64M(kernel),1M(atf),1M(optee)" + PARTS="$PARTS,1M(recovery_dtb),74M(recovery),30380032K(rootfs)" + CMDLINE='console=ttyS0,115200n8 earlycon=uart8250,mmio32,0x2016000' + CMDLINE="$CMDLINE root=/dev/mmcblk0p12 rootfstype=ext4 rw rootwait" + CMDLINE="$CMDLINE blkdevparts=$PARTS" + CMDLINE="$CMDLINE systemd.show_status=yes systemd.log_target=console" + + for input in "$QEMU" "$KERNEL" "$EMMC"; do + if [ ! -r "$input" ]; then + echo "missing input: $input" >&2 + exit 1 + fi + done + + exec "$QEMU" \ + -machine ax650x-pyramid \ + -accel tcg,thread=multi \ + -cpu cortex-a55 \ + -smp 8 \ + -m 2G \ + -kernel "$KERNEL" \ + -append "$CMDLINE" \ + -drive "file=$EMMC,if=sd,format=raw,snapshot=on" \ + -chardev stdio,id=serial0,signal=off \ + -serial chardev:serial0 \ + -display none \ + -monitor none \ + -no-reboot + +``snapshot=on`` keeps the reusable eMMC image unchanged. Remove it only when +persistent guest writes are intentional. + +The downstream model has been exercised with an AXERA Linux 5.15.73 kernel +and Ubuntu 22.04.5 LTS. Linux enumerates all eight CPUs, probes the eMMC using +64-bit ADMA, creates the twelve fixed partitions, and mounts +``/dev/mmcblk0p12`` as the writable root filesystem. + +Known limitations +----------------- + +* Only QEMU direct kernel boot is implemented. BootROM, SPL, U-Boot, Arm + Trusted Firmware and OP-TEE images are not loaded or executed. +* NPU, VDSP, RISC-V auxiliary cores, video, ISP, display, audio, USB, PCIe, + SATA and the full clock, reset and power-management trees are not modeled. + GPIO behavior is limited to the DesignWare APB subset needed for PHY reset. +* DWMAC TSO, PTP/TSN, multi-queue performance fidelity and analog PHY timing + are not modeled. +* The UART extension window implements the probe-time subset. The AXERA + driver can report a harmless capability mismatch because the UART component + version register intentionally selects its compatible fallback path. +* QEMU direct boot starts CPUs at EL2. The target firmware starts Linux at + EL1, so the vendor kernel can print a non-fatal GICv2 CPU-interface range + warning under QEMU. +* The SDHCI vendor bank implements the registers required for probe, reset and + data transfer. HS200/HS400 timing accuracy is not claimed. + +Running tests +------------- + +The board qtest covers CPU topology, RAM, GICv2, UART MMIO and IRQ behavior, +SDHCI vendor registers, reset, block I/O, eMMC IRQ routing, 64-bit ADMA, +Auto CMD23, DWMAC synthesis registers, MDIO, GPIO and reset glue, hardware +spinlocks, 40-bit DMA and Ethernet IRQ routing:: + + $ meson test -C build qemu:qtest-aarch64/ax650x-pyramid-test \ + --print-errorlogs + +The Ubuntu quick-boot functional test downloads a pinned kernel and compressed +qcow2 image from the `AX650X Ubuntu 22.04 QEMU assets release +`__. +The functional asset layer verifies both SHA-256 digests. The test uses +``snapshot=on`` and waits for DWMAC probe, the eMMC partition map, the mounted +root filesystem, Ubuntu readiness markers and the serial login prompt: + +.. code-block:: shell + + $ meson test -C build \ + --suite thorough \ + func-aarch64-ax650x_ubuntu \ + --print-errorlogs + +The test is registered in the ``thorough`` functional suite because the +kernel and Ubuntu image are external assets. A cached copy is reused only +after its declared content hash has been checked. diff --git a/docs/system/target-arm.rst b/docs/system/target-arm.rst index 8d36a9e180a22..be665576ab862 100644 --- a/docs/system/target-arm.rst +++ b/docs/system/target-arm.rst @@ -72,6 +72,7 @@ Board-specific documentation :maxdepth: 1 arm/max78000 + arm/ax650x-pyramid arm/integratorcp arm/mps2 arm/musca diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 8cd2ab6db8432..2726fc56d08a5 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -37,6 +37,20 @@ config ARM_VIRT select ACPI_CXL select ACPI_HMAT +config AX650X_PYRAMID + bool + default y + depends on TCG && AARCH64 + select ARM_GIC + select AX650X_DWMAC_GLUE + select AX650X_HWSPINLOCK + select AX650X_SDHCI + select DEVICE_TREE + select DW_APB_GPIO + select DWMAC4 + select SERIAL_MM + select UNIMP + config CUBIEBOARD bool default y diff --git a/hw/arm/ax650x-dwmac.c b/hw/arm/ax650x-dwmac.c new file mode 100644 index 0000000000000..0fa672758fc0f --- /dev/null +++ b/hw/arm/ax650x-dwmac.c @@ -0,0 +1,317 @@ +/* + * AX650X DWMAC board integration + * + * This file owns the networking-related board topology and FDT nodes. The + * reusable devices themselves live in hw/net, hw/gpio, and hw/misc. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "hw/arm/ax650x-dwmac.h" +#include "hw/arm/fdt.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/sysbus.h" +#include "hw/gpio/dw-apb-gpio.h" +#include "hw/misc/ax650x-dwmac-glue.h" +#include "hw/misc/ax650x-hwspinlock.h" +#include "hw/misc/unimp.h" +#include "hw/net/dwmac4.h" +#include "net/net.h" +#include "system/device_tree.h" + +#define AX650X_DWMAC_NR 2 +#define AX650X_DWMAC0_BASE 0x10140000 +#define AX650X_DWMAC1_BASE 0x30800000 +#define AX650X_DWMAC0_IRQ 104 +#define AX650X_DWMAC1_IRQ 186 +#define AX650X_DWMAC0_GLB_BASE 0x10000000 +#define AX650X_DWMAC1_GLB_BASE 0x30000000 +#define AX650X_DWMAC0_CLK_BASE 0x10010000 +#define AX650X_DWMAC1_CLK_BASE 0x30010000 +#define AX650X_GPIO0_BASE 0x02003000 +#define AX650X_GPIO1_BASE 0x02004000 +#define AX650X_HWSPINLOCK_BASE 0x04510000 +#define AX650X_DPHY_BASE 0x13c00000 +#define AX650X_DPHY_SIZE 0x00080000 + +#define AX650X_DWMAC0_CLOCK_HZ 400000000 +#define AX650X_DWMAC1_CLOCK_HZ 500000000 +#define AX650X_EPHY_CLOCK_HZ 25000000 +#define AX650X_RGMII_CLOCK_HZ 125000000 +#define AX650X_RMII_CLOCK_HZ 50000000 +#define AX650X_PTP_CLOCK_HZ 50000000 +#define AX650X_HWSPINLOCK_CLOCK_HZ 200000000 + +static const hwaddr dwmac_base[AX650X_DWMAC_NR] = { + AX650X_DWMAC0_BASE, AX650X_DWMAC1_BASE, +}; + +static const unsigned int dwmac_irq[AX650X_DWMAC_NR] = { + AX650X_DWMAC0_IRQ, AX650X_DWMAC1_IRQ, +}; + +static const hwaddr dwmac_glb_base[AX650X_DWMAC_NR] = { + AX650X_DWMAC0_GLB_BASE, AX650X_DWMAC1_GLB_BASE, +}; + +static const hwaddr dwmac_clk_base[AX650X_DWMAC_NR] = { + AX650X_DWMAC0_CLK_BASE, AX650X_DWMAC1_CLK_BASE, +}; + +static const hwaddr gpio_base[AX650X_DWMAC_NR] = { + AX650X_GPIO0_BASE, AX650X_GPIO1_BASE, +}; + +static const char *const eth_path[AX650X_DWMAC_NR] = { + "/soc/ethernet@10140000", + "/soc/ethernet@30800000", +}; + +static const char *const gpio_path[AX650X_DWMAC_NR] = { + "/soc/gpio@2003000", + "/soc/gpio@2004000", +}; + +void ax650x_dwmac_create(DeviceState *gic) +{ + DeviceState *hwspinlock = qdev_new(TYPE_AX650X_HWSPINLOCK); + SysBusDevice *sbd = SYS_BUS_DEVICE(hwspinlock); + + sysbus_realize_and_unref(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, AX650X_HWSPINLOCK_BASE); + + /* + * The vendor AXERA GPIO driver reads all eight DPHY windows before it + * registers GPIO lines. DPHY behavior is not part of networking, but a + * named unimplemented window keeps those discovery reads non-fatal. + */ + create_unimplemented_device("ax650x.dphy", AX650X_DPHY_BASE, + AX650X_DPHY_SIZE); + + for (unsigned int i = 0; i < AX650X_DWMAC_NR; i++) { + DeviceState *gpio = qdev_new(TYPE_DW_APB_GPIO); + DeviceState *glue = qdev_new(TYPE_AX650X_DWMAC_GLUE); + DeviceState *mac = qdev_new(TYPE_DWMAC4); + + sbd = SYS_BUS_DEVICE(gpio); + sysbus_realize_and_unref(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, gpio_base[i]); + + qdev_prop_set_uint8(glue, "port", i); + sbd = SYS_BUS_DEVICE(glue); + sysbus_realize_and_unref(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, dwmac_glb_base[i]); + sysbus_mmio_map(sbd, 1, dwmac_clk_base[i]); + + qemu_configure_nic_device(mac, i == 0, i == 0 ? "gmac0" : "gmac1"); + qdev_prop_set_uint8(mac, "snps-version", 0x52); + qdev_prop_set_uint8(mac, "user-version", 0x10); + qdev_prop_set_uint8(mac, "dma-width", 40); + qdev_prop_set_uint8(mac, "phy-addr", 1); + qdev_prop_set_uint16(mac, "phy-id1", 0x937c); + qdev_prop_set_uint16(mac, "phy-id2", 0x4030); + qdev_prop_set_uint32(mac, "rx-fifo-size", 65536); + qdev_prop_set_uint32(mac, "tx-fifo-size", 32768); + sbd = SYS_BUS_DEVICE(mac); + sysbus_realize_and_unref(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, dwmac_base[i]); + sysbus_connect_irq(sbd, 0, qdev_get_gpio_in(gic, dwmac_irq[i])); + } +} + +static uint32_t ax650x_fdt_add_fixed_clock(void *fdt, const char *path, + uint32_t frequency) +{ + uint32_t phandle; + + qemu_fdt_add_subnode(fdt, path); + qemu_fdt_setprop_string(fdt, path, "compatible", "fixed-clock"); + qemu_fdt_setprop_cell(fdt, path, "#clock-cells", 0); + qemu_fdt_setprop_cell(fdt, path, "clock-frequency", frequency); + phandle = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_setprop_cell(fdt, path, "phandle", phandle); + + return phandle; +} + +static void ax650x_dwmac_add_phy_fdt(void *fdt, const char *path, + uint32_t *phandle) +{ + qemu_fdt_add_subnode(fdt, path); + qemu_fdt_setprop_string(fdt, path, "compatible", + "ethernet-phy-id937c.4030"); + qemu_fdt_setprop_cell(fdt, path, "reg", 1); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,led-enable", 0x5); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,led-mode", 0x6251); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,led-period", 0x3); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,led-on", 0x2); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,led-polarity", 0x1c00); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,patch-enable", 0x1); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,rgmii-enable", 0x5); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,clk-enable", 0x9); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,fld-enable", 0x3); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,fld-delay", 0); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,wol-enable", 0x3); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,interrupt-enable", 0x5); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,downshift-enable", 0x3); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,downshift-count", 0x3); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,work_mode-enable", 0x1); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,work_mode-mode", 0); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,lpbk-enable", 0x1); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,lpbk-mode", 0x2); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,slew_rate-enable", 0x1); + qemu_fdt_setprop_cell(fdt, path, "jl2xxx,rxc_out-enable", 0x1); + *phandle = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_setprop_cell(fdt, path, "phandle", *phandle); +} + +void ax650x_dwmac_create_fdt(void *fdt) +{ + static const unsigned int phy_reset_pin[AX650X_DWMAC_NR] = { 7, 11 }; + const char clock_names[] = + "emac_aclk\0ephy_clk\0rgmii_tx_clk\0rmii_phy_clk\0rmii_rx_clk\0" + "ptp_clk"; + uint32_t dwmac_clock_phandles[AX650X_DWMAC_NR]; + uint32_t gpio_phandles[AX650X_DWMAC_NR]; + uint32_t ephy_clock_phandle; + uint32_t rgmii_clock_phandle; + uint32_t rmii_clock_phandle; + uint32_t ptp_clock_phandle; + uint32_t hwspinlock_clock_phandle; + uint32_t stmmac_axi_phandle; + uint32_t mtl_rx_phandle; + uint32_t mtl_tx_phandle; + + qemu_fdt_setprop_string(fdt, "/aliases", "ethernet0", eth_path[0]); + qemu_fdt_setprop_string(fdt, "/aliases", "ethernet1", eth_path[1]); + + dwmac_clock_phandles[0] = ax650x_fdt_add_fixed_clock( + fdt, "/dwmac0-clock", AX650X_DWMAC0_CLOCK_HZ); + dwmac_clock_phandles[1] = ax650x_fdt_add_fixed_clock( + fdt, "/dwmac1-clock", AX650X_DWMAC1_CLOCK_HZ); + ephy_clock_phandle = ax650x_fdt_add_fixed_clock( + fdt, "/ephy-clock", AX650X_EPHY_CLOCK_HZ); + rgmii_clock_phandle = ax650x_fdt_add_fixed_clock( + fdt, "/rgmii-clock", AX650X_RGMII_CLOCK_HZ); + rmii_clock_phandle = ax650x_fdt_add_fixed_clock( + fdt, "/rmii-clock", AX650X_RMII_CLOCK_HZ); + ptp_clock_phandle = ax650x_fdt_add_fixed_clock( + fdt, "/ptp-clock", AX650X_PTP_CLOCK_HZ); + hwspinlock_clock_phandle = ax650x_fdt_add_fixed_clock( + fdt, "/hwspinlock-clock", AX650X_HWSPINLOCK_CLOCK_HZ); + + qemu_fdt_add_subnode(fdt, "/soc/ax_hwspinlock@4510000"); + qemu_fdt_setprop_string(fdt, "/soc/ax_hwspinlock@4510000", "compatible", + "axera,hwspinlock-r1p0"); + qemu_fdt_setprop_sized_cells(fdt, "/soc/ax_hwspinlock@4510000", "reg", + 2, AX650X_HWSPINLOCK_BASE, + 2, AX650X_HWSPINLOCK_MMIO_SIZE); + qemu_fdt_setprop_cell(fdt, "/soc/ax_hwspinlock@4510000", "clocks", + hwspinlock_clock_phandle); + qemu_fdt_setprop_string(fdt, "/soc/ax_hwspinlock@4510000", "clock-names", + "spinlock_pclk"); + qemu_fdt_setprop_string(fdt, "/soc/ax_hwspinlock@4510000", "status", + "okay"); + + qemu_fdt_add_subnode(fdt, "/soc/stmmac-axi-config"); + qemu_fdt_setprop_cells(fdt, "/soc/stmmac-axi-config", + "snps,wr_osr_lmt", 15); + qemu_fdt_setprop_cells(fdt, "/soc/stmmac-axi-config", + "snps,rd_osr_lmt", 15); + qemu_fdt_setprop_cells(fdt, "/soc/stmmac-axi-config", "snps,blen", + 0, 0, 0, 32, 16, 8, 4); + stmmac_axi_phandle = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_setprop_cell(fdt, "/soc/stmmac-axi-config", "phandle", + stmmac_axi_phandle); + + qemu_fdt_add_subnode(fdt, "/soc/rx-queues-config"); + qemu_fdt_setprop_cell(fdt, "/soc/rx-queues-config", + "snps,rx-queues-to-use", 1); + qemu_fdt_add_subnode(fdt, "/soc/rx-queues-config/queue0"); + mtl_rx_phandle = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_setprop_cell(fdt, "/soc/rx-queues-config", "phandle", + mtl_rx_phandle); + + qemu_fdt_add_subnode(fdt, "/soc/tx-queues-config"); + qemu_fdt_setprop_cell(fdt, "/soc/tx-queues-config", + "snps,tx-queues-to-use", 1); + qemu_fdt_add_subnode(fdt, "/soc/tx-queues-config/queue0"); + mtl_tx_phandle = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_setprop_cell(fdt, "/soc/tx-queues-config", "phandle", + mtl_tx_phandle); + + for (unsigned int i = 0; i < AX650X_DWMAC_NR; i++) { + g_autofree char *gpio_port_path = + g_strdup_printf("%s/gpio-controller@0", gpio_path[i]); + g_autofree char *mdio_path = g_strdup_printf("%s/mdio", eth_path[i]); + g_autofree char *phy_path = g_strdup_printf("%s/jl2xxx-phy@1", + mdio_path); + uint32_t phy_phandle; + + qemu_fdt_add_subnode(fdt, gpio_path[i]); + qemu_fdt_setprop_string(fdt, gpio_path[i], "compatible", + "snps,ax-apb-gpio"); + qemu_fdt_setprop_cell(fdt, gpio_path[i], "#address-cells", 1); + qemu_fdt_setprop_cell(fdt, gpio_path[i], "#size-cells", 0); + qemu_fdt_setprop_sized_cells(fdt, gpio_path[i], "reg", + 2, gpio_base[i], + 2, DW_APB_GPIO_MMIO_SIZE); + if (i == 0) { + qemu_fdt_setprop_cell(fdt, gpio_path[i], "hwlock_id", 12); + } + qemu_fdt_setprop_string(fdt, gpio_path[i], "status", "okay"); + + qemu_fdt_add_subnode(fdt, gpio_port_path); + qemu_fdt_setprop_string(fdt, gpio_port_path, "compatible", + "snps,dw-apb-gpio-port"); + qemu_fdt_setprop_cell(fdt, gpio_port_path, "reg", 0); + qemu_fdt_setprop(fdt, gpio_port_path, "gpio-controller", NULL, 0); + qemu_fdt_setprop_cell(fdt, gpio_port_path, "#gpio-cells", 2); + qemu_fdt_setprop_cell(fdt, gpio_port_path, "snps,nr-gpios", + DW_APB_GPIO_NR_PINS); + gpio_phandles[i] = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_setprop_cell(fdt, gpio_port_path, "phandle", + gpio_phandles[i]); + + qemu_fdt_add_subnode(fdt, eth_path[i]); + qemu_fdt_setprop_string(fdt, eth_path[i], "compatible", + "axera,dwmac-4.10a"); + qemu_fdt_setprop_sized_cells(fdt, eth_path[i], "reg", + 2, dwmac_base[i], + 2, DWMAC4_MMIO_SIZE); + qemu_fdt_setprop_cells(fdt, eth_path[i], "interrupts", + GIC_FDT_IRQ_TYPE_SPI, dwmac_irq[i], + GIC_FDT_IRQ_FLAGS_LEVEL_HI); + qemu_fdt_setprop_string(fdt, eth_path[i], "interrupt-names", + "macirq"); + qemu_fdt_setprop_cells(fdt, eth_path[i], "clocks", + dwmac_clock_phandles[i], ephy_clock_phandle, + rgmii_clock_phandle, rmii_clock_phandle, + rmii_clock_phandle, ptp_clock_phandle); + qemu_fdt_setprop(fdt, eth_path[i], "clock-names", clock_names, + sizeof(clock_names)); + qemu_fdt_setprop_cell(fdt, eth_path[i], "rx-fifo-depth", 65536); + qemu_fdt_setprop_cell(fdt, eth_path[i], "tx-fifo-depth", 32768); + qemu_fdt_setprop_cell(fdt, eth_path[i], "snps,axi-config", + stmmac_axi_phandle); + qemu_fdt_setprop_cell(fdt, eth_path[i], "snps,mtl-rx-config", + mtl_rx_phandle); + qemu_fdt_setprop_cell(fdt, eth_path[i], "snps,mtl-tx-config", + mtl_tx_phandle); + qemu_fdt_setprop_cells(fdt, eth_path[i], "phy-rst-gpio", + gpio_phandles[i], phy_reset_pin[i], 0); + qemu_fdt_setprop_string(fdt, eth_path[i], "phy-mode", "rgmii"); + qemu_fdt_setprop_string(fdt, eth_path[i], "status", "okay"); + + qemu_fdt_add_subnode(fdt, mdio_path); + qemu_fdt_setprop_string(fdt, mdio_path, "compatible", + "snps,dwmac-mdio"); + qemu_fdt_setprop_cell(fdt, mdio_path, "#address-cells", 1); + qemu_fdt_setprop_cell(fdt, mdio_path, "#size-cells", 0); + + ax650x_dwmac_add_phy_fdt(fdt, phy_path, &phy_phandle); + qemu_fdt_setprop_cell(fdt, eth_path[i], "phy-handle", phy_phandle); + } +} diff --git a/hw/arm/ax650x.c b/hw/arm/ax650x.c new file mode 100644 index 0000000000000..d68ccfd299ba2 --- /dev/null +++ b/hw/arm/ax650x.c @@ -0,0 +1,551 @@ +/* + * Axera AX650X AI Pyramid machine + * + * Copyright (c) 2026 Zevorn + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "qemu/units.h" +#include "qapi/error.h" +#include "hw/arm/ax650x-dwmac.h" +#include "hw/arm/boot.h" +#include "hw/arm/bsa.h" +#include "hw/arm/fdt.h" +#include "hw/arm/machines-qom.h" +#include "hw/char/serial-mm.h" +#include "hw/core/boards.h" +#include "hw/core/cpu.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/sysbus.h" +#include "hw/intc/arm_gic.h" +#include "hw/sd/ax650x-sdhci.h" +#include "hw/sd/sd.h" +#include "system/address-spaces.h" +#include "system/blockdev.h" +#include "system/device_tree.h" +#include "system/system.h" +#include "target/arm/cpu.h" +#include "target/arm/cpu-qom.h" +#include "target/arm/gtimer.h" + +#define TYPE_AX650X_PYRAMID_MACHINE MACHINE_TYPE_NAME("ax650x-pyramid") +OBJECT_DECLARE_SIMPLE_TYPE(AX650XPyramidState, AX650X_PYRAMID_MACHINE) + +#define AX650X_NUM_CPUS 8 +#define AX650X_NUM_IRQS 256 +#define AX650X_NUM_SPIS (AX650X_NUM_IRQS - GIC_INTERNAL) + +#define AX650X_RAM_BASE 0x100000000ULL +#define AX650X_RAM_SIZE (2 * GiB) + +#define AX650X_GIC_DIST_BASE 0x04901000 +#define AX650X_GIC_DIST_SIZE 0x1000 +#define AX650X_GIC_CPU_BASE 0x04902000 +#define AX650X_GIC_CPU_SIZE 0x1000 +#define AX650X_GIC_HYP_BASE 0x04904000 +#define AX650X_GIC_HYP_SIZE 0x2000 +#define AX650X_GIC_VCPU_BASE 0x04906000 +#define AX650X_GIC_VCPU_SIZE 0x2000 + +#define AX650X_UART0_BASE 0x02016000 +#define AX650X_UART0_SIZE 0x400 +#define AX650X_UART_STD_SIZE 0x20 +#define AX650X_UART_USR 0x7c +#define AX650X_UART_UCV 0xf8 +#define AX650X_UART_USR_TFNF BIT(1) +#define AX650X_UART_USR_TFE BIT(2) +#define AX650X_UART0_IRQ 135 +#define AX650X_UART_CLOCK_HZ 200000000 +#define AX650X_UART_BAUDBASE (AX650X_UART_CLOCK_HZ / 16) + +#define AX650X_EMMC_BASE 0x28000000 +#define AX650X_EMMC_SIZE 0x600 +#define AX650X_EMMC_IRQ 93 +#define AX650X_EMMC_CLOCK_HZ 200000000 + +#define AX650X_TIMER_CLOCK_HZ 24000000 +#define AX650X_PMU_IRQ_BASE 80 +#define AX650X_GIC_MAINT_PPI 9 +#define AX650X_GIC_PPI_FLAGS 0xff04 + +struct AX650XPyramidState { + MachineState parent_obj; + + ARMCPU *cpus[AX650X_NUM_CPUS]; + DeviceState *gic; + MemoryRegion gic_cpu_alias; + MemoryRegion uart0_ext; + struct arm_boot_info bootinfo; + void *fdt; + int fdt_size; +}; + +static uint64_t ax650x_uart_ext_read(void *opaque, hwaddr offset, + unsigned int size) +{ + hwaddr reg = AX650X_UART_STD_SIZE + offset; + + switch (reg) { + case AX650X_UART_USR: + return AX650X_UART_USR_TFNF | AX650X_UART_USR_TFE; + case AX650X_UART_UCV: + /* + * The hardware component revision is not documented. Zero selects + * the AXERA driver's defined fallback for a UART without DesignWare + * additional features. + */ + return 0; + default: + return 0; + } +} + +static void ax650x_uart_ext_write(void *opaque, hwaddr offset, + uint64_t value, unsigned int size) +{ +} + +static const MemoryRegionOps ax650x_uart_ext_ops = { + .read = ax650x_uart_ext_read, + .write = ax650x_uart_ext_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + }, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + }, +}; + +static uint64_t ax650x_cpu_mpidr(unsigned int cpu) +{ + return (uint64_t)cpu << 8; +} + +static void ax650x_create_cpus(AX650XPyramidState *s) +{ + MachineState *machine = MACHINE(s); + MemoryRegion *sysmem = get_system_memory(); + + for (unsigned int i = 0; i < machine->smp.cpus; i++) { + Object *cpuobj = object_new(machine->cpu_type); + CPUState *cs = CPU(cpuobj); + + cs->cpu_index = i; + object_property_set_int(cpuobj, "mp-affinity", + ax650x_cpu_mpidr(i), &error_abort); + object_property_set_int(cpuobj, "cntfrq", AX650X_TIMER_CLOCK_HZ, + &error_abort); + object_property_set_bool(cpuobj, "has_el3", false, &error_abort); + object_property_set_link(cpuobj, "memory", OBJECT(sysmem), + &error_abort); + qdev_realize(DEVICE(cpuobj), NULL, &error_fatal); + s->cpus[i] = ARM_CPU(cpuobj); + object_unref(cpuobj); + } +} + +static void ax650x_create_gic(AX650XPyramidState *s) +{ + MachineState *machine = MACHINE(s); + MemoryRegion *sysmem = get_system_memory(); + SysBusDevice *gicbus; + + s->gic = qdev_new(gic_class_name()); + qdev_prop_set_uint32(s->gic, "revision", 2); + qdev_prop_set_uint32(s->gic, "num-cpu", machine->smp.cpus); + qdev_prop_set_uint32(s->gic, "num-irq", AX650X_NUM_IRQS); + qdev_prop_set_bit(s->gic, "has-security-extensions", false); + qdev_prop_set_bit(s->gic, "has-virtualization-extensions", true); + + gicbus = SYS_BUS_DEVICE(s->gic); + sysbus_realize_and_unref(gicbus, &error_fatal); + sysbus_mmio_map(gicbus, 0, AX650X_GIC_DIST_BASE); + + /* + * QEMU exposes a 0x2000-byte GICC region for GICv2, while AX650X + * decodes only the architected 0x1000-byte CPU interface window. + */ + memory_region_init_alias(&s->gic_cpu_alias, OBJECT(s), + "ax650x.gic-cpu", + sysbus_mmio_get_region(gicbus, 1), + 0, AX650X_GIC_CPU_SIZE); + memory_region_add_subregion(sysmem, AX650X_GIC_CPU_BASE, + &s->gic_cpu_alias); + sysbus_mmio_map(gicbus, 2, AX650X_GIC_HYP_BASE); + sysbus_mmio_map(gicbus, 3, AX650X_GIC_VCPU_BASE); + + for (unsigned int i = 0; i < machine->smp.cpus; i++) { + DeviceState *cpu = DEVICE(s->cpus[i]); + int ppi_base = AX650X_NUM_SPIS + i * GIC_INTERNAL; + + qdev_connect_gpio_out(cpu, GTIMER_PHYS, + qdev_get_gpio_in(s->gic, ppi_base + + ARCH_TIMER_NS_EL1_IRQ)); + qdev_connect_gpio_out(cpu, GTIMER_VIRT, + qdev_get_gpio_in(s->gic, ppi_base + + ARCH_TIMER_VIRT_IRQ)); + qdev_connect_gpio_out(cpu, GTIMER_HYP, + qdev_get_gpio_in(s->gic, ppi_base + + ARCH_TIMER_NS_EL2_IRQ)); + qdev_connect_gpio_out(cpu, GTIMER_SEC, + qdev_get_gpio_in(s->gic, ppi_base + + ARCH_TIMER_S_EL1_IRQ)); + + sysbus_connect_irq(gicbus, i, + qdev_get_gpio_in(cpu, ARM_CPU_IRQ)); + sysbus_connect_irq(gicbus, i + machine->smp.cpus, + qdev_get_gpio_in(cpu, ARM_CPU_FIQ)); + sysbus_connect_irq(gicbus, i + 2 * machine->smp.cpus, + qdev_get_gpio_in(cpu, ARM_CPU_VIRQ)); + sysbus_connect_irq(gicbus, i + 3 * machine->smp.cpus, + qdev_get_gpio_in(cpu, ARM_CPU_VFIQ)); + sysbus_connect_irq(gicbus, i + 4 * machine->smp.cpus, + qdev_get_gpio_in(s->gic, + ppi_base + AX650X_GIC_MAINT_PPI)); + + qdev_connect_gpio_out_named(cpu, "pmu-interrupt", 0, + qdev_get_gpio_in(s->gic, + AX650X_PMU_IRQ_BASE + i)); + } +} + +static void ax650x_create_emmc(AX650XPyramidState *s) +{ + DeviceState *host = qdev_new(TYPE_AX650X_SDHCI); + SysBusDevice *sbd = SYS_BUS_DEVICE(host); + DriveInfo *dinfo = drive_get(IF_SD, 0, 0); + + sysbus_realize_and_unref(sbd, &error_fatal); + sysbus_mmio_map(sbd, 0, AX650X_EMMC_BASE); + sysbus_connect_irq(sbd, 0, + qdev_get_gpio_in(s->gic, AX650X_EMMC_IRQ)); + + if (dinfo) { + DeviceState *card = qdev_new(TYPE_EMMC); + + qdev_prop_set_drive_err(card, "drive", blk_by_legacy_dinfo(dinfo), + &error_fatal); + qdev_realize_and_unref(card, + BUS(ax650x_sdhci_get_bus(AX650X_SDHCI(host))), + &error_fatal); + } +} + +static void ax650x_create_fdt(AX650XPyramidState *s) +{ + MachineState *machine = MACHINE(s); + uint32_t cpu_phandles[AX650X_NUM_CPUS]; + uint32_t gic_phandle; + uint32_t uart_baud_phandle; + uint32_t uart_apb_phandle; + uint32_t emmc_clock_phandle; + const char clock_names[] = "baudclk\0apb_pclk"; + const char emmc_clock_names[] = "aclk\0hclk\0cardclk"; + const char uart_path[] = "/soc/ax_uart@2016000"; + const char emmc_path[] = "/soc/sdhc@28000000"; + + s->fdt = create_device_tree(&s->fdt_size); + if (!s->fdt) { + error_report("ax650x-pyramid: create_device_tree() failed"); + exit(EXIT_FAILURE); + } + + qemu_fdt_setprop_string(s->fdt, "/", "compatible", "axera,ax650x"); + qemu_fdt_setprop_string(s->fdt, "/", "model", + "M5Stack AI Pyramid (AX650X)"); + qemu_fdt_setprop_cell(s->fdt, "/", "#address-cells", 2); + qemu_fdt_setprop_cell(s->fdt, "/", "#size-cells", 2); + + qemu_fdt_add_subnode(s->fdt, "/aliases"); + qemu_fdt_add_subnode(s->fdt, "/chosen"); + qemu_fdt_setprop_string(s->fdt, "/aliases", "serial0", uart_path); + qemu_fdt_setprop_string(s->fdt, "/chosen", "stdout-path", uart_path); + + qemu_fdt_add_subnode(s->fdt, "/cpus"); + qemu_fdt_setprop_cell(s->fdt, "/cpus", "#address-cells", 1); + qemu_fdt_setprop_cell(s->fdt, "/cpus", "#size-cells", 0); + for (int i = machine->smp.cpus - 1; i >= 0; i--) { + g_autofree char *node = g_strdup_printf("/cpus/cpu@%x", + i << 8); + + qemu_fdt_add_subnode(s->fdt, node); + qemu_fdt_setprop_string(s->fdt, node, "device_type", "cpu"); + qemu_fdt_setprop_string(s->fdt, node, "compatible", + "arm,cortex-a55"); + qemu_fdt_setprop_cell(s->fdt, node, "reg", ax650x_cpu_mpidr(i)); + qemu_fdt_setprop_string(s->fdt, node, "enable-method", "psci"); + cpu_phandles[i] = qemu_fdt_alloc_phandle(s->fdt); + qemu_fdt_setprop_cell(s->fdt, node, "phandle", cpu_phandles[i]); + } + + qemu_fdt_add_subnode(s->fdt, "/psci"); + qemu_fdt_setprop_string(s->fdt, "/psci", "compatible", "arm,psci-1.0"); + qemu_fdt_setprop_string(s->fdt, "/psci", "method", "smc"); + + qemu_fdt_add_subnode(s->fdt, "/timer"); + qemu_fdt_setprop_string(s->fdt, "/timer", "compatible", + "arm,armv8-timer"); + qemu_fdt_setprop_cells(s->fdt, "/timer", "interrupts", + GIC_FDT_IRQ_TYPE_PPI, + INTID_TO_PPI(ARCH_TIMER_S_EL1_IRQ), + AX650X_GIC_PPI_FLAGS, + GIC_FDT_IRQ_TYPE_PPI, + INTID_TO_PPI(ARCH_TIMER_NS_EL1_IRQ), + AX650X_GIC_PPI_FLAGS, + GIC_FDT_IRQ_TYPE_PPI, + INTID_TO_PPI(ARCH_TIMER_VIRT_IRQ), + AX650X_GIC_PPI_FLAGS, + GIC_FDT_IRQ_TYPE_PPI, + INTID_TO_PPI(ARCH_TIMER_NS_EL2_IRQ), + AX650X_GIC_PPI_FLAGS); + qemu_fdt_setprop_cell(s->fdt, "/timer", "clock-frequency", + AX650X_TIMER_CLOCK_HZ); + + qemu_fdt_add_subnode(s->fdt, "/interrupt-controller@4900000"); + qemu_fdt_setprop_string(s->fdt, "/interrupt-controller@4900000", + "compatible", "arm,gic-400"); + qemu_fdt_setprop_cell(s->fdt, "/interrupt-controller@4900000", + "#interrupt-cells", 3); + qemu_fdt_setprop_cell(s->fdt, "/interrupt-controller@4900000", + "#address-cells", 2); + qemu_fdt_setprop_cell(s->fdt, "/interrupt-controller@4900000", + "#size-cells", 2); + qemu_fdt_setprop(s->fdt, "/interrupt-controller@4900000", + "interrupt-controller", NULL, 0); + qemu_fdt_setprop(s->fdt, "/interrupt-controller@4900000", "ranges", + NULL, 0); + qemu_fdt_setprop_sized_cells(s->fdt, "/interrupt-controller@4900000", + "reg", + 2, AX650X_GIC_DIST_BASE, + 2, AX650X_GIC_DIST_SIZE, + 2, AX650X_GIC_CPU_BASE, + 2, AX650X_GIC_CPU_SIZE, + 2, AX650X_GIC_HYP_BASE, + 2, AX650X_GIC_HYP_SIZE, + 2, AX650X_GIC_VCPU_BASE, + 2, AX650X_GIC_VCPU_SIZE); + qemu_fdt_setprop_cells(s->fdt, "/interrupt-controller@4900000", + "interrupts", GIC_FDT_IRQ_TYPE_PPI, + AX650X_GIC_MAINT_PPI, AX650X_GIC_PPI_FLAGS); + gic_phandle = qemu_fdt_alloc_phandle(s->fdt); + qemu_fdt_setprop_cell(s->fdt, "/interrupt-controller@4900000", + "phandle", gic_phandle); + qemu_fdt_setprop_cell(s->fdt, "/", "interrupt-parent", gic_phandle); + + qemu_fdt_add_subnode(s->fdt, "/pmu"); + qemu_fdt_setprop_string(s->fdt, "/pmu", "compatible", + "arm,cortex-a55-pmu"); + qemu_fdt_setprop_cells(s->fdt, "/pmu", "interrupts", + GIC_FDT_IRQ_TYPE_SPI, 80, + GIC_FDT_IRQ_FLAGS_LEVEL_HI, + GIC_FDT_IRQ_TYPE_SPI, 81, + GIC_FDT_IRQ_FLAGS_LEVEL_HI, + GIC_FDT_IRQ_TYPE_SPI, 82, + GIC_FDT_IRQ_FLAGS_LEVEL_HI, + GIC_FDT_IRQ_TYPE_SPI, 83, + GIC_FDT_IRQ_FLAGS_LEVEL_HI, + GIC_FDT_IRQ_TYPE_SPI, 84, + GIC_FDT_IRQ_FLAGS_LEVEL_HI, + GIC_FDT_IRQ_TYPE_SPI, 85, + GIC_FDT_IRQ_FLAGS_LEVEL_HI, + GIC_FDT_IRQ_TYPE_SPI, 86, + GIC_FDT_IRQ_FLAGS_LEVEL_HI, + GIC_FDT_IRQ_TYPE_SPI, 87, + GIC_FDT_IRQ_FLAGS_LEVEL_HI); + qemu_fdt_setprop_cells(s->fdt, "/pmu", "interrupt-affinity", + cpu_phandles[0], cpu_phandles[1], + cpu_phandles[2], cpu_phandles[3], + cpu_phandles[4], cpu_phandles[5], + cpu_phandles[6], cpu_phandles[7]); + + qemu_fdt_add_subnode(s->fdt, "/uart-baud-clock"); + qemu_fdt_setprop_string(s->fdt, "/uart-baud-clock", "compatible", + "fixed-clock"); + qemu_fdt_setprop_cell(s->fdt, "/uart-baud-clock", "#clock-cells", 0); + qemu_fdt_setprop_cell(s->fdt, "/uart-baud-clock", "clock-frequency", + AX650X_UART_CLOCK_HZ); + uart_baud_phandle = qemu_fdt_alloc_phandle(s->fdt); + qemu_fdt_setprop_cell(s->fdt, "/uart-baud-clock", "phandle", + uart_baud_phandle); + + qemu_fdt_add_subnode(s->fdt, "/uart-apb-clock"); + qemu_fdt_setprop_string(s->fdt, "/uart-apb-clock", "compatible", + "fixed-clock"); + qemu_fdt_setprop_cell(s->fdt, "/uart-apb-clock", "#clock-cells", 0); + qemu_fdt_setprop_cell(s->fdt, "/uart-apb-clock", "clock-frequency", + AX650X_UART_CLOCK_HZ); + uart_apb_phandle = qemu_fdt_alloc_phandle(s->fdt); + qemu_fdt_setprop_cell(s->fdt, "/uart-apb-clock", "phandle", + uart_apb_phandle); + + qemu_fdt_add_subnode(s->fdt, "/emmc-clock"); + qemu_fdt_setprop_string(s->fdt, "/emmc-clock", "compatible", + "fixed-clock"); + qemu_fdt_setprop_cell(s->fdt, "/emmc-clock", "#clock-cells", 0); + qemu_fdt_setprop_cell(s->fdt, "/emmc-clock", "clock-frequency", + AX650X_EMMC_CLOCK_HZ); + emmc_clock_phandle = qemu_fdt_alloc_phandle(s->fdt); + qemu_fdt_setprop_cell(s->fdt, "/emmc-clock", "phandle", + emmc_clock_phandle); + + qemu_fdt_add_subnode(s->fdt, "/soc"); + qemu_fdt_setprop_string(s->fdt, "/soc", "compatible", "simple-bus"); + qemu_fdt_setprop_cell(s->fdt, "/soc", "#address-cells", 2); + qemu_fdt_setprop_cell(s->fdt, "/soc", "#size-cells", 2); + qemu_fdt_setprop(s->fdt, "/soc", "ranges", NULL, 0); + ax650x_dwmac_create_fdt(s->fdt); + + qemu_fdt_add_subnode(s->fdt, uart_path); + qemu_fdt_setprop_string(s->fdt, uart_path, "compatible", + "axera,ax-apb-uart"); + qemu_fdt_setprop_sized_cells(s->fdt, uart_path, "reg", + 2, AX650X_UART0_BASE, + 2, AX650X_UART0_SIZE); + qemu_fdt_setprop_cell(s->fdt, uart_path, "reg-shift", 2); + qemu_fdt_setprop_cell(s->fdt, uart_path, "reg-io-width", 4); + qemu_fdt_setprop_cells(s->fdt, uart_path, "clocks", + uart_baud_phandle, uart_apb_phandle); + qemu_fdt_setprop(s->fdt, uart_path, "clock-names", clock_names, + sizeof(clock_names)); + qemu_fdt_setprop_cell(s->fdt, uart_path, "default_cpr_reg", 0x425f2); + qemu_fdt_setprop_cells(s->fdt, uart_path, "interrupts", + GIC_FDT_IRQ_TYPE_SPI, AX650X_UART0_IRQ, + GIC_FDT_IRQ_FLAGS_LEVEL_HI); + qemu_fdt_setprop_string(s->fdt, uart_path, "status", "okay"); + + qemu_fdt_add_subnode(s->fdt, emmc_path); + qemu_fdt_setprop_string(s->fdt, emmc_path, "compatible", + "axera,sdhc-ax650"); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "#address-cells", 2); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "#size-cells", 2); + qemu_fdt_setprop_sized_cells(s->fdt, emmc_path, "reg", + 2, AX650X_EMMC_BASE, + 2, AX650X_EMMC_SIZE); + qemu_fdt_setprop_cells(s->fdt, emmc_path, "interrupts", + GIC_FDT_IRQ_TYPE_SPI, AX650X_EMMC_IRQ, + GIC_FDT_IRQ_FLAGS_LEVEL_HI); + qemu_fdt_setprop_cells(s->fdt, emmc_path, "clocks", + emmc_clock_phandle, emmc_clock_phandle, + emmc_clock_phandle); + qemu_fdt_setprop(s->fdt, emmc_path, "clock-names", + emmc_clock_names, sizeof(emmc_clock_names)); + qemu_fdt_setprop_cells(s->fdt, emmc_path, "sdhci-caps-mask", + 0x2, 0x03200000); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "bus-width", 8); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "max-frequency", + AX650X_EMMC_CLOCK_HZ); + qemu_fdt_setprop(s->fdt, emmc_path, "cap-mmc-hw-reset", NULL, 0); + qemu_fdt_setprop(s->fdt, emmc_path, "cap-mmc-highspeed", NULL, 0); + qemu_fdt_setprop(s->fdt, emmc_path, "mmc-hs200-1_8v", NULL, 0); + qemu_fdt_setprop(s->fdt, emmc_path, "mmc-hs400-1_8v", NULL, 0); + qemu_fdt_setprop(s->fdt, emmc_path, "mmc-hs400-enhanced-strobe", + NULL, 0); + qemu_fdt_setprop(s->fdt, emmc_path, "no-sdio", NULL, 0); + qemu_fdt_setprop(s->fdt, emmc_path, "no-sd", NULL, 0); + qemu_fdt_setprop(s->fdt, emmc_path, "non-removable", NULL, 0); + qemu_fdt_setprop(s->fdt, emmc_path, "disable-wp", NULL, 0); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-cnfg", 0x00cc0000); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-cmdpad-cnfg", + 0x0449); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-datapad-cnfg", + 0x0449); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-clkpad-cnfg", + 0x0440); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-stbpad-cnfg", + 0x0451); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-rstnpad-cnfg", + 0x0449); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-commdl-cnfg", 0); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-sdclkdl-cnfg", 1); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-sdclkdl-dc", 0x7f); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-smpldl-cnfg", 0xc); + qemu_fdt_setprop_cell(s->fdt, emmc_path, "axera,phy-atdl-cnfg", 0xc); + qemu_fdt_setprop_string(s->fdt, emmc_path, "status", "okay"); +} + +static void *ax650x_get_dtb(const struct arm_boot_info *binfo, int *fdt_size) +{ + AX650XPyramidState *s = container_of(binfo, AX650XPyramidState, + bootinfo); + + *fdt_size = s->fdt_size; + return s->fdt; +} + +static void ax650x_pyramid_init(MachineState *machine) +{ + AX650XPyramidState *s = AX650X_PYRAMID_MACHINE(machine); + MemoryRegion *sysmem = get_system_memory(); + + if (machine->ram_size != AX650X_RAM_SIZE) { + error_report("ax650x-pyramid: RAM size must be exactly 2 GiB"); + exit(EXIT_FAILURE); + } + + memory_region_add_subregion(sysmem, AX650X_RAM_BASE, machine->ram); + ax650x_create_cpus(s); + ax650x_create_gic(s); + ax650x_create_emmc(s); + ax650x_dwmac_create(s->gic); + + serial_mm_init(sysmem, AX650X_UART0_BASE, 2, + qdev_get_gpio_in(s->gic, AX650X_UART0_IRQ), + AX650X_UART_BAUDBASE, serial_hd(0), DEVICE_LITTLE_ENDIAN); + memory_region_init_io(&s->uart0_ext, OBJECT(s), &ax650x_uart_ext_ops, s, + "ax650x.uart0-ext", + AX650X_UART0_SIZE - AX650X_UART_STD_SIZE); + memory_region_add_subregion(sysmem, + AX650X_UART0_BASE + AX650X_UART_STD_SIZE, + &s->uart0_ext); + + ax650x_create_fdt(s); + + s->bootinfo.ram_size = machine->ram_size; + s->bootinfo.board_id = -1; + s->bootinfo.loader_start = AX650X_RAM_BASE; + s->bootinfo.get_dtb = ax650x_get_dtb; + s->bootinfo.psci_conduit = QEMU_PSCI_CONDUIT_SMC; + arm_load_kernel(s->cpus[0], machine, &s->bootinfo); +} + +static void ax650x_pyramid_class_init(ObjectClass *oc, const void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + static const char * const valid_cpu_types[] = { + ARM_CPU_TYPE_NAME("cortex-a55"), + NULL, + }; + + mc->desc = "M5Stack AI Pyramid (Axera AX650X)"; + mc->init = ax650x_pyramid_init; + mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-a55"); + mc->valid_cpu_types = valid_cpu_types; + mc->default_cpus = AX650X_NUM_CPUS; + mc->min_cpus = AX650X_NUM_CPUS; + mc->max_cpus = AX650X_NUM_CPUS; + mc->default_ram_size = AX650X_RAM_SIZE; + mc->default_ram_id = "ax650x.ram"; + mc->no_cdrom = true; + mc->no_floppy = true; +} + +static const TypeInfo ax650x_pyramid_info = { + .name = TYPE_AX650X_PYRAMID_MACHINE, + .parent = TYPE_MACHINE, + .instance_size = sizeof(AX650XPyramidState), + .class_init = ax650x_pyramid_class_init, + .interfaces = aarch64_machine_interfaces, +}; + +static void ax650x_pyramid_machine_register_types(void) +{ + type_register_static(&ax650x_pyramid_info); +} + +type_init(ax650x_pyramid_machine_register_types) diff --git a/hw/arm/meson.build b/hw/arm/meson.build index 9024a304f10f6..2b7d24fca66a4 100644 --- a/hw/arm/meson.build +++ b/hw/arm/meson.build @@ -1,5 +1,7 @@ arm_common_ss = ss.source_set() arm_common_ss.add(when: 'CONFIG_ARM_VIRT', if_true: files('virt.c')) +arm_common_ss.add(when: 'CONFIG_AX650X_PYRAMID', + if_true: files('ax650x.c', 'ax650x-dwmac.c')) arm_common_ss.add(when: 'CONFIG_ACPI', if_true: files('virt-acpi-build.c')) arm_common_ss.add(when: 'CONFIG_DIGIC', if_true: files('digic_boards.c')) arm_common_ss.add(when: 'CONFIG_EMCRAFT_SF2', if_true: files('msf2-som.c')) diff --git a/hw/gpio/Kconfig b/hw/gpio/Kconfig index 9d66fd5202f1e..d8f12794f7254 100644 --- a/hw/gpio/Kconfig +++ b/hw/gpio/Kconfig @@ -1,6 +1,9 @@ config PL061 bool +config DW_APB_GPIO + bool + config GPIO_KEY bool diff --git a/hw/gpio/dw-apb-gpio.c b/hw/gpio/dw-apb-gpio.c new file mode 100644 index 0000000000000..2113aa2224a16 --- /dev/null +++ b/hw/gpio/dw-apb-gpio.c @@ -0,0 +1,132 @@ +/* + * Synopsys DesignWare APB GPIO + * + * This local model implements the single-port data, direction, and external + * value registers used by Linux gpio-dwapb. Interrupts and additional ports + * are deliberately outside its current contract. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/core/registerfields.h" +#include "hw/gpio/dw-apb-gpio.h" +#include "migration/vmstate.h" +#include "qemu/module.h" + +REG32(SWPORTA_DR, 0x00) +REG32(SWPORTA_DDR, 0x04) +REG32(EXT_PORTA, 0x50) + +static void dw_apb_gpio_update(DWAPBGPIOState *s) +{ + uint32_t data = s->regs[R_SWPORTA_DR]; + uint32_t direction = s->regs[R_SWPORTA_DDR]; + + s->regs[R_EXT_PORTA] = (data & direction) | (s->input & ~direction); + for (unsigned int i = 0; i < DW_APB_GPIO_NR_PINS; i++) { + qemu_set_irq(s->output[i], + (direction & BIT(i)) && (data & BIT(i))); + } +} + +static void dw_apb_gpio_data_postw(RegisterInfo *reg, uint64_t value) +{ + dw_apb_gpio_update(DW_APB_GPIO(reg->opaque)); +} + +static const RegisterAccessInfo dw_apb_gpio_regs_info[] = { + { .name = "SWPORTA_DR", .addr = A_SWPORTA_DR, + .post_write = dw_apb_gpio_data_postw, + }, + { .name = "SWPORTA_DDR", .addr = A_SWPORTA_DDR, + .post_write = dw_apb_gpio_data_postw, + }, + { .name = "EXT_PORTA", .addr = A_EXT_PORTA, + .ro = MAKE_64BIT_MASK(0, 32), + }, +}; + +static const MemoryRegionOps dw_apb_gpio_ops = { + .read = register_read_memory, + .write = register_write_memory, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void dw_apb_gpio_set(void *opaque, int line, int level) +{ + DWAPBGPIOState *s = opaque; + + s->input = deposit32(s->input, line, 1, !!level); + dw_apb_gpio_update(s); +} + +static void dw_apb_gpio_reset(DeviceState *dev) +{ + DWAPBGPIOState *s = DW_APB_GPIO(dev); + + for (unsigned int i = 0; i < DW_APB_GPIO_NR_REGS; i++) { + register_reset(&s->regs_info[i]); + } + s->input = 0; + dw_apb_gpio_update(s); +} + +static int dw_apb_gpio_post_load(void *opaque, int version_id) +{ + dw_apb_gpio_update(DW_APB_GPIO(opaque)); + return 0; +} + +static const VMStateDescription vmstate_dw_apb_gpio = { + .name = TYPE_DW_APB_GPIO, + .version_id = 1, + .minimum_version_id = 1, + .post_load = dw_apb_gpio_post_load, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, DWAPBGPIOState, DW_APB_GPIO_NR_REGS), + VMSTATE_UINT32(input, DWAPBGPIOState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void dw_apb_gpio_init(Object *obj) +{ + DWAPBGPIOState *s = DW_APB_GPIO(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + s->reg_array = register_init_block32(DEVICE(obj), + dw_apb_gpio_regs_info, + ARRAY_SIZE(dw_apb_gpio_regs_info), + s->regs_info, s->regs, + &dw_apb_gpio_ops, false, + DW_APB_GPIO_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->reg_array->mem); + qdev_init_gpio_in(DEVICE(obj), dw_apb_gpio_set, DW_APB_GPIO_NR_PINS); + qdev_init_gpio_out(DEVICE(obj), s->output, DW_APB_GPIO_NR_PINS); +} + +static void dw_apb_gpio_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "Synopsys DesignWare APB GPIO"; + device_class_set_legacy_reset(dc, dw_apb_gpio_reset); + dc->vmsd = &vmstate_dw_apb_gpio; +} + +static const TypeInfo dw_apb_gpio_types[] = { + { + .name = TYPE_DW_APB_GPIO, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(DWAPBGPIOState), + .instance_init = dw_apb_gpio_init, + .class_init = dw_apb_gpio_class_init, + }, +}; +DEFINE_TYPES(dw_apb_gpio_types) diff --git a/hw/gpio/meson.build b/hw/gpio/meson.build index 301d57fa26998..fe890ab89e3be 100644 --- a/hw/gpio/meson.build +++ b/hw/gpio/meson.build @@ -1,4 +1,5 @@ system_ss.add(when: 'CONFIG_GPIO_KEY', if_true: files('gpio_key.c')) +system_ss.add(when: 'CONFIG_DW_APB_GPIO', if_true: files('dw-apb-gpio.c')) system_ss.add(when: 'CONFIG_GPIO_MPC8XXX', if_true: files('mpc8xxx.c')) system_ss.add(when: 'CONFIG_GPIO_PWR', if_true: files('gpio_pwr.c')) system_ss.add(when: 'CONFIG_PCA9552', if_true: files('pca9552.c')) diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index 55db0c38ebfd8..34512f6f4996e 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -342,3 +342,9 @@ config SOPHGO_CV1800B_CLK bool source macio/Kconfig +# AX650X DWMAC clock/reset/interface glue +config AX650X_DWMAC_GLUE + bool + +config AX650X_HWSPINLOCK + bool diff --git a/hw/misc/ax650x-dwmac-glue.c b/hw/misc/ax650x-dwmac-glue.c new file mode 100644 index 0000000000000..8840f1eef005c --- /dev/null +++ b/hw/misc/ax650x-dwmac-glue.c @@ -0,0 +1,165 @@ +/* + * Axera AX650X DWMAC clock/reset/interface glue + * + * The register locations and bit assignments are extracted from the vendor + * Linux 5.15.73 dwmac-axera driver. This model stores the RGMII interface, + * speed mux, and clock enable controls and implements software-reset set/clear + * aliases. It does not model clock waveforms or RGMII timing. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/registerfields.h" +#include "hw/misc/ax650x-dwmac-glue.h" +#include "migration/vmstate.h" +#include "qapi/error.h" +#include "qemu/module.h" + +REG32(GLB_EMAC1_PHY_IF, 0x94) +REG32(GLB_EMAC0_PHY_IF, 0x9c) + +REG32(CLK_MUX, 0x00) +REG32(CLK_ENABLE0, 0x04) +REG32(CLK_ENABLE1, 0x08) +REG32(CLK_DIV, 0x0c) +REG32(SW_RESET0, 0x10) +REG32(SW_RESET0_SET, 0x38) +REG32(SW_RESET0_CLEAR, 0x3c) + +static void ax650x_dwmac_reset_set_postw(RegisterInfo *reg, uint64_t value) +{ + AX650XDWMACGlueState *s = AX650X_DWMAC_GLUE(reg->opaque); + uint32_t mask = BIT(s->port ? 2 : 7); + + s->clk_regs[R_SW_RESET0] |= value & mask; + s->clk_regs[R_SW_RESET0_SET] = 0; +} + +static void ax650x_dwmac_reset_clear_postw(RegisterInfo *reg, uint64_t value) +{ + AX650XDWMACGlueState *s = AX650X_DWMAC_GLUE(reg->opaque); + uint32_t mask = BIT(s->port ? 2 : 7); + + s->clk_regs[R_SW_RESET0] &= ~(value & mask); + s->clk_regs[R_SW_RESET0_CLEAR] = 0; +} + +static const RegisterAccessInfo ax650x_dwmac_glb0_regs_info[] = { + { .name = "EMAC0_PHY_IF", .addr = A_GLB_EMAC0_PHY_IF }, +}; + +static const RegisterAccessInfo ax650x_dwmac_glb1_regs_info[] = { + { .name = "EMAC1_PHY_IF", .addr = A_GLB_EMAC1_PHY_IF }, +}; + +static const RegisterAccessInfo ax650x_dwmac_clk_regs_info[] = { + { .name = "CLK_MUX", .addr = A_CLK_MUX }, + { .name = "CLK_ENABLE0", .addr = A_CLK_ENABLE0 }, + { .name = "CLK_ENABLE1", .addr = A_CLK_ENABLE1 }, + { .name = "CLK_DIV", .addr = A_CLK_DIV }, + { .name = "SW_RESET0", .addr = A_SW_RESET0, + .ro = MAKE_64BIT_MASK(0, 32), + }, + { .name = "SW_RESET0_SET", .addr = A_SW_RESET0_SET, + .post_write = ax650x_dwmac_reset_set_postw, + }, + { .name = "SW_RESET0_CLEAR", .addr = A_SW_RESET0_CLEAR, + .post_write = ax650x_dwmac_reset_clear_postw, + }, +}; + +static const MemoryRegionOps ax650x_dwmac_glue_ops = { + .read = register_read_memory, + .write = register_write_memory, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void ax650x_dwmac_glue_reset(DeviceState *dev) +{ + AX650XDWMACGlueState *s = AX650X_DWMAC_GLUE(dev); + + for (unsigned int i = 0; i < AX650X_DWMAC_GLB_NR_REGS; i++) { + register_reset(&s->glb_regs_info[i]); + } + for (unsigned int i = 0; i < AX650X_DWMAC_CLK_NR_REGS; i++) { + register_reset(&s->clk_regs_info[i]); + } +} + +static void ax650x_dwmac_glue_realize(DeviceState *dev, Error **errp) +{ + AX650XDWMACGlueState *s = AX650X_DWMAC_GLUE(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + const RegisterAccessInfo *glb_regs; + size_t glb_nr_regs; + + if (s->port > 1) { + error_setg(errp, "ax650x-dwmac-glue: port must be 0 or 1"); + return; + } + + if (s->port == 0) { + glb_regs = ax650x_dwmac_glb0_regs_info; + glb_nr_regs = ARRAY_SIZE(ax650x_dwmac_glb0_regs_info); + } else { + glb_regs = ax650x_dwmac_glb1_regs_info; + glb_nr_regs = ARRAY_SIZE(ax650x_dwmac_glb1_regs_info); + } + + s->glb_reg_array = register_init_block32( + dev, glb_regs, glb_nr_regs, s->glb_regs_info, s->glb_regs, + &ax650x_dwmac_glue_ops, false, AX650X_DWMAC_GLUE_MMIO_SIZE); + s->clk_reg_array = register_init_block32( + dev, ax650x_dwmac_clk_regs_info, + ARRAY_SIZE(ax650x_dwmac_clk_regs_info), s->clk_regs_info, + s->clk_regs, &ax650x_dwmac_glue_ops, false, + AX650X_DWMAC_GLUE_MMIO_SIZE); + + sysbus_init_mmio(sbd, &s->glb_reg_array->mem); + sysbus_init_mmio(sbd, &s->clk_reg_array->mem); +} + +static const VMStateDescription vmstate_ax650x_dwmac_glue = { + .name = TYPE_AX650X_DWMAC_GLUE, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32_ARRAY(glb_regs, AX650XDWMACGlueState, + AX650X_DWMAC_GLB_NR_REGS), + VMSTATE_UINT32_ARRAY(clk_regs, AX650XDWMACGlueState, + AX650X_DWMAC_CLK_NR_REGS), + VMSTATE_END_OF_LIST(), + }, +}; + +static const Property ax650x_dwmac_glue_properties[] = { + DEFINE_PROP_UINT8("port", AX650XDWMACGlueState, port, 0), +}; + +static void ax650x_dwmac_glue_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "AX650X DWMAC clock/reset/interface glue"; + dc->realize = ax650x_dwmac_glue_realize; + device_class_set_legacy_reset(dc, ax650x_dwmac_glue_reset); + dc->vmsd = &vmstate_ax650x_dwmac_glue; + device_class_set_props(dc, ax650x_dwmac_glue_properties); +} + +static const TypeInfo ax650x_dwmac_glue_types[] = { + { + .name = TYPE_AX650X_DWMAC_GLUE, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AX650XDWMACGlueState), + .class_init = ax650x_dwmac_glue_class_init, + }, +}; +DEFINE_TYPES(ax650x_dwmac_glue_types) diff --git a/hw/misc/ax650x-hwspinlock.c b/hw/misc/ax650x-hwspinlock.c new file mode 100644 index 0000000000000..e6c92d928ddbc --- /dev/null +++ b/hw/misc/ax650x-hwspinlock.c @@ -0,0 +1,109 @@ +/* + * AXERA AX650X hardware spinlock + * + * The AXERA Linux driver exposes 32 locks. Each lock has an acquire register + * at lock_id * 8 and an unlock register at lock_id * 8 + 4. Reading an + * unlocked acquire register claims the lock for CPU master ID 0 and returns + * that ID (zero); a later read reports the lock busy. The vendor driver + * converts the zero owner ID into a successful hwspinlock trylock result. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/misc/ax650x-hwspinlock.h" +#include "migration/vmstate.h" +#include "qemu/bitops.h" +#include "qemu/module.h" + +#define AX650X_HWSPINLOCK_STRIDE 8 + +static uint64_t ax650x_hwspinlock_read(void *opaque, hwaddr offset, + unsigned int size) +{ + AX650XHWSpinlockState *s = AX650X_HWSPINLOCK(opaque); + unsigned int lock = offset / AX650X_HWSPINLOCK_STRIDE; + + if (lock >= AX650X_HWSPINLOCK_COUNT || + offset % AX650X_HWSPINLOCK_STRIDE != 0) { + return 0; + } + + if (s->locked & BIT(lock)) { + return 1; + } + + s->locked |= BIT(lock); + return 0; +} + +static void ax650x_hwspinlock_write(void *opaque, hwaddr offset, + uint64_t value, unsigned int size) +{ + AX650XHWSpinlockState *s = AX650X_HWSPINLOCK(opaque); + unsigned int lock = offset / AX650X_HWSPINLOCK_STRIDE; + + if (lock < AX650X_HWSPINLOCK_COUNT && + offset % AX650X_HWSPINLOCK_STRIDE == sizeof(uint32_t)) { + s->locked &= ~BIT(lock); + } +} + +static const MemoryRegionOps ax650x_hwspinlock_ops = { + .read = ax650x_hwspinlock_read, + .write = ax650x_hwspinlock_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void ax650x_hwspinlock_reset(DeviceState *dev) +{ + AX650XHWSpinlockState *s = AX650X_HWSPINLOCK(dev); + + s->locked = 0; +} + +static void ax650x_hwspinlock_init(Object *obj) +{ + AX650XHWSpinlockState *s = AX650X_HWSPINLOCK(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + memory_region_init_io(&s->iomem, obj, &ax650x_hwspinlock_ops, s, + TYPE_AX650X_HWSPINLOCK, + AX650X_HWSPINLOCK_MMIO_SIZE); + sysbus_init_mmio(sbd, &s->iomem); +} + +static const VMStateDescription vmstate_ax650x_hwspinlock = { + .name = TYPE_AX650X_HWSPINLOCK, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT32(locked, AX650XHWSpinlockState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void ax650x_hwspinlock_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->desc = "AX650X hardware spinlock"; + device_class_set_legacy_reset(dc, ax650x_hwspinlock_reset); + dc->vmsd = &vmstate_ax650x_hwspinlock; +} + +static const TypeInfo ax650x_hwspinlock_types[] = { + { + .name = TYPE_AX650X_HWSPINLOCK, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AX650XHWSpinlockState), + .instance_init = ax650x_hwspinlock_init, + .class_init = ax650x_hwspinlock_class_init, + }, +}; +DEFINE_TYPES(ax650x_hwspinlock_types) diff --git a/hw/misc/meson.build b/hw/misc/meson.build index e92e19236194c..a1953b05acd1d 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -196,3 +196,7 @@ system_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa_ec.c')) # HPPA devices system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c')) +system_ss.add(when: 'CONFIG_AX650X_DWMAC_GLUE', + if_true: files('ax650x-dwmac-glue.c')) +system_ss.add(when: 'CONFIG_AX650X_HWSPINLOCK', + if_true: files('ax650x-hwspinlock.c')) diff --git a/hw/net/dwmac4.c b/hw/net/dwmac4.c index 7ad434a3addeb..0f1be0214d0a0 100644 --- a/hw/net/dwmac4.c +++ b/hw/net/dwmac4.c @@ -8,7 +8,7 @@ * (dwmac4.h / dwmac4_dma.h / dwmac4_descs.h in the Linux stmmac driver): * * - MAC register bank 0x000..0x3ff (MAC_CONFIG, PACKET_FILTER, ADDR slots, - * MDIO, HW_FEATURE0..3, MAC_VERSION @ 0x110 = SNPSVER 0x51). + * MDIO, HW_FEATURE0..3, and board-selectable MAC_VERSION @ 0x110. * - DMA register bank 0x1000..0x11ff (DMA_BUS_MODE, per-channel block at * 0x1100 + chan*0x80: TX/RX_CONTROL, TX/RX_BASE_ADDR, RING_LEN, * INTR_ENA, CHAN_STATUS as W1C with 4.10-layout NIS/AIS). @@ -30,6 +30,7 @@ #include "net/checksum.h" #include "net/eth.h" #include "net/net.h" +#include "qapi/error.h" #include "qemu/bitops.h" #include "qemu/log.h" #include "qemu/module.h" @@ -98,6 +99,7 @@ REG32(DMA_CHAN_TX_CONTROL, 0x04) FIELD(DMA_CHAN_TX_CONTROL, ST, 0, 1) REG32(DMA_CHAN_RX_CONTROL, 0x08) FIELD(DMA_CHAN_RX_CONTROL, SR, 0, 1) + FIELD(DMA_CHAN_RX_CONTROL, RBSZ, 1, 14) REG32(DMA_CHAN_TX_BASE_ADDR_HI, 0x10) REG32(DMA_CHAN_TX_BASE_ADDR, 0x14) REG32(DMA_CHAN_RX_BASE_ADDR_HI, 0x18) @@ -168,6 +170,7 @@ REG32(DMA_CHAN_STATUS, 0x60) #define DESC_OWN BIT(31) #define TX_DESC3_FIRST BIT(29) #define TX_DESC3_LAST BIT(28) +#define TX_DESC3_CIC_MASK 0x00030000u #define TX_DESC2_IOC BIT(31) #define TX_DESC2_B1SZ_MASK 0x00003fffu /* BUFFER1_SIZE[13:0] */ #define RX_DESC3_FIRST BIT(29) @@ -258,23 +261,26 @@ static void dwmac4_update_irq(DWMAC4State *s) static void dwmac4_try_send(DWMAC4State *s, int chan) { + g_autoptr(GByteArray) frame = NULL; + bool frame_checksum = false; hwaddr base_addr = ((uint64_t)s->dma_regs[dwmac4_chan_reg(chan, A_DMA_CHAN_TX_BASE_ADDR_HI) / 4] << 32) | s->dma_regs[dwmac4_chan_reg(chan, A_DMA_CHAN_TX_BASE_ADDR) / 4]; uint32_t ring_len = s->dma_regs[dwmac4_chan_reg(chan, A_DMA_CHAN_TX_RING_LEN) / 4]; - if (!base_addr || !ring_len) { + uint64_t ring_entries = (uint64_t)ring_len + 1; + if (!base_addr) { return; } NetClientState *nc = qemu_get_queue(s->nic); - uint32_t cur = s->tx_desc_cur[chan]; + hwaddr cur = s->tx_desc_cur[chan]; if (!cur) { cur = base_addr; } - for (uint32_t i = 0; i < ring_len; i++) { + for (uint64_t i = 0; i < ring_entries; i++) { uint32_t d[4]; if (dwmac4_read_desc(cur, d)) { return; @@ -282,12 +288,13 @@ static void dwmac4_try_send(DWMAC4State *s, int chan) /* OWN=1 -> MAC owns, can TX. Otherwise ring drained for now. */ if (!(d[3] & DESC_OWN)) { - return; + break; } bool first = d[3] & TX_DESC3_FIRST; bool last = d[3] & TX_DESC3_LAST; bool ioc = d[2] & TX_DESC2_IOC; + bool checksum_insertion = d[3] & TX_DESC3_CIC_MASK; uint32_t b1sz = d[2] & TX_DESC2_B1SZ_MASK; /* * dwmac4 TDES0 holds buffer-1 address (low 32). In extended 64-bit @@ -299,24 +306,39 @@ static void dwmac4_try_send(DWMAC4State *s, int chan) */ hwaddr b1addr = ((uint64_t)d[1] << 32) | d[0]; - if (first && last) { - /* Common case: single-buffer frame in one descriptor. */ - g_autofree uint8_t *buf = NULL; - if (b1sz && b1sz <= 65536) { - buf = g_malloc(b1sz); - if (dma_memory_read(&address_space_memory, b1addr, buf, b1sz, - MEMTXATTRS_UNSPECIFIED)) { - qemu_log_mask(LOG_GUEST_ERROR, - "dwmac4: TX buffer read failed @ 0x%" - HWADDR_PRIx "\n", b1addr); - g_free(buf); - return; - } - net_checksum_calculate(buf, b1sz, 0); - qemu_send_packet(nc, buf, b1sz); + if (first) { + g_clear_pointer(&frame, g_byte_array_unref); + frame = g_byte_array_new(); + frame_checksum = checksum_insertion; + } + + if (frame && b1sz) { + size_t offset = frame->len; + + if (offset + b1sz > 65536) { + qemu_log_mask(LOG_GUEST_ERROR, + "dwmac4: TX frame exceeds 64 KiB\n"); + return; + } + g_byte_array_set_size(frame, offset + b1sz); + if (dma_memory_read(&address_space_memory, b1addr, + frame->data + offset, b1sz, + MEMTXATTRS_UNSPECIFIED)) { + qemu_log_mask(LOG_GUEST_ERROR, + "dwmac4: TX buffer read failed @ 0x%" + HWADDR_PRIx "\n", b1addr); + return; } } + if (last && frame) { + if (frame_checksum) { + net_checksum_calculate(frame->data, frame->len, CSUM_ALL); + } + qemu_send_packet(nc, frame->data, frame->len); + g_clear_pointer(&frame, g_byte_array_unref); + } + /* Write-back: clear OWN. Error summary (bit15) left clear. */ d[3] &= ~DESC_OWN; if (dwmac4_write_desc(cur, d)) { @@ -324,7 +346,7 @@ static void dwmac4_try_send(DWMAC4State *s, int chan) } cur += 16; - if (cur >= base_addr + (uint64_t)ring_len * 16) { + if (cur >= base_addr + ring_entries * 16) { cur = base_addr; } @@ -335,10 +357,6 @@ static void dwmac4_try_send(DWMAC4State *s, int chan) dwmac4_update_irq(s); } - if (last) { - /* Stop after a complete frame so the guest can observe IRQ. */ - break; - } } s->tx_desc_cur[chan] = cur; @@ -379,17 +397,18 @@ static ssize_t dwmac4_receive(NetClientState *nc, const uint8_t *buf, size_t len A_DMA_CHAN_RX_BASE_ADDR) / 4]; uint32_t ring_len = s->dma_regs[dwmac4_chan_reg(chan, A_DMA_CHAN_RX_RING_LEN) / 4]; - if (!base_addr || !ring_len) { + uint64_t ring_entries = (uint64_t)ring_len + 1; + if (!base_addr) { return len; /* no ring posted yet - silently drop */ } - uint32_t cur = s->rx_desc_cur[chan]; + hwaddr cur = s->rx_desc_cur[chan]; if (!cur) { cur = base_addr; } /* Find the next descriptor the MAC owns. */ - for (uint32_t i = 0; i < ring_len; i++) { + for (uint64_t i = 0; i < ring_entries; i++) { uint32_t d[4]; if (dwmac4_read_desc(cur, d)) { return len; @@ -400,8 +419,10 @@ static ssize_t dwmac4_receive(NetClientState *nc, const uint8_t *buf, size_t len } hwaddr b1addr = ((uint64_t)d[1] << 32) | d[0]; - /* RDES2[14:0] is BUFFER1_SIZE; we cap the write to that. */ - uint32_t b1sz = d[2] & 0x3fff; + /* DWMAC4 keeps the receive buffer size in DMA_CHAN_RX_CONTROL. */ + uint32_t rxctl = s->dma_regs[dwmac4_chan_reg( + chan, A_DMA_CHAN_RX_CONTROL) / 4]; + uint32_t b1sz = FIELD_EX32(rxctl, DMA_CHAN_RX_CONTROL, RBSZ); uint32_t to_write = MIN((uint32_t)len, b1sz); if (to_write && dma_memory_write(&address_space_memory, b1addr, buf, @@ -418,14 +439,20 @@ static ssize_t dwmac4_receive(NetClientState *nc, const uint8_t *buf, size_t len */ d[3] &= ~DESC_OWN; d[3] &= ~RX_DESC3_PKT_SIZE_MASK; - d[3] |= RX_DESC3_FIRST | RX_DESC3_LAST | ((uint32_t)len & - RX_DESC3_PKT_SIZE_MASK); + /* + * GMAC4 reports a packet size that includes the four-byte Ethernet + * FCS, although the DMA buffer does not contain the FCS. stmmac + * accounts for that contract by subtracting ETH_FCS_LEN before it + * hands the skb to the network stack. + */ + d[3] |= RX_DESC3_FIRST | RX_DESC3_LAST | + (((uint32_t)len + ETH_FCS_LEN) & RX_DESC3_PKT_SIZE_MASK); if (dwmac4_write_desc(cur, d)) { return len; } cur += 16; - if (cur >= base_addr + (uint64_t)ring_len * 16) { + if (cur >= base_addr + ring_entries * 16) { cur = base_addr; } s->rx_desc_cur[chan] = cur; @@ -485,11 +512,7 @@ static void dwmac4_mdio(DWMAC4State *s, uint32_t v) s->mac_regs[R_GMAC_MDIO_ADDR] = v; return; } - if (pa > 1) { - /* - * The ROC-RK3588S-PC DT uses PHY address 1. Keep address 0 alive - * for the simple QEMU DT and report all other addresses as idle. - */ + if (pa != s->phy_addr) { if (!write) { s->mac_regs[R_GMAC_MDIO_DATA] = 0xffff; } @@ -499,9 +522,20 @@ static void dwmac4_mdio(DWMAC4State *s, uint32_t v) if (write) { data = s->mac_regs[R_GMAC_MDIO_DATA] & 0xffff; - s->phy_regs[gr] = data; + if (gr == 31) { + s->phy_page = data; + } else if (s->phy_page == 0) { + /* BMCR reset and autoneg restart are self-clearing commands. */ + s->phy_regs[gr] = gr == 0 ? data & ~(BIT(15) | BIT(9)) : data; + } } else { - s->mac_regs[R_GMAC_MDIO_DATA] = s->phy_regs[gr]; + if (gr == 31) { + s->mac_regs[R_GMAC_MDIO_DATA] = s->phy_page; + } else { + /* Vendor pages are accepted as RAZ/WI; page 0 is clause-22. */ + s->mac_regs[R_GMAC_MDIO_DATA] = + s->phy_page == 0 ? s->phy_regs[gr] : 0; + } } /* BUSY is self-clearing in hardware; model that. */ @@ -698,7 +732,7 @@ static const RegisterAccessInfo mac_regs_info[] = { .ro = MAKE_64BIT_MASK(0, 32), }, { .name = "GMAC4_VERSION", .addr = A_GMAC4_VERSION, - .reset = DWMAC4_VERSION_RESET, + .reset = DWMAC4_DEFAULT_SNPS_VERSION, .ro = MAKE_64BIT_MASK(0, 32), }, { .name = "GMAC_DEBUG", .addr = A_GMAC_DEBUG, @@ -710,7 +744,8 @@ static const RegisterAccessInfo mac_regs_info[] = { * dwmac4.h: RXCOESEL=16, TXCOSEL=14, TSSEL=12, MMCSEL=8, MGKSEL=7. */ { .name = "GMAC_HW_FEATURE0", .addr = A_GMAC_HW_FEATURE0, - .reset = BIT(16) | BIT(14) | BIT(12) | BIT(8) | BIT(7), + .reset = BIT(16) | BIT(14) | BIT(12) | BIT(8) | BIT(7) | BIT(6) | + BIT(5) | BIT(1) | BIT(0), .ro = MAKE_64BIT_MASK(0, 32), }, { .name = "GMAC_HW_FEATURE1", .addr = A_GMAC_HW_FEATURE1, @@ -954,6 +989,17 @@ static void dwmac4_reset(DeviceState *dev) s->rx_desc_cur[chan] = 0; } memcpy(s->phy_regs, phy_reg_init, sizeof(s->phy_regs)); + s->phy_page = 0; + s->phy_regs[2] = s->phy_id1; + s->phy_regs[3] = s->phy_id2; + + s->mac_regs[R_GMAC4_VERSION] = + ((uint32_t)s->user_version << 8) | s->snps_version; + s->mac_regs[R_GMAC_HW_FEATURE1] = + ((s->dma_width == 40 ? 1 : s->dma_width == 48 ? 2 : 0) << 14) | + ((ctz32(s->tx_fifo_size) - 7) << 6) | + (ctz32(s->rx_fifo_size) - 7) | + (s->tso ? BIT(18) : 0); /* Reflect the configured MAC address into MAC_ADDR_HIGH0/LOW0. */ s->mac_regs[R_GMAC_ADDR_HIGH0] = 0x80000000 | @@ -970,6 +1016,27 @@ static void dwmac4_realize(DeviceState *dev, Error **errp) DWMAC4State *s = DWMAC4(dev); SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + if (s->dma_width != 32 && s->dma_width != 40 && s->dma_width != 48) { + error_setg(errp, "dwmac4: dma-width must be 32, 40, or 48"); + return; + } + if (!is_power_of_2(s->tx_fifo_size) || s->tx_fifo_size < 128 || + s->tx_fifo_size > 4 * MiB) { + error_setg(errp, "dwmac4: tx-fifo-size must be a power of two " + "between 128 bytes and 4 MiB"); + return; + } + if (!is_power_of_2(s->rx_fifo_size) || s->rx_fifo_size < 128 || + s->rx_fifo_size > 4 * MiB) { + error_setg(errp, "dwmac4: rx-fifo-size must be a power of two " + "between 128 bytes and 4 MiB"); + return; + } + if (s->phy_addr > 31) { + error_setg(errp, "dwmac4: phy-addr must be in the range 0..31"); + return; + } + /* * Two register blocks, each giving us a MemoryRegion + RegisterInfo * array. We keep references to the arrays so our top-level dispatcher @@ -1006,20 +1073,34 @@ static void dwmac4_unrealize(DeviceState *dev) static const VMStateDescription vmstate_dwmac4 = { .name = TYPE_DWMAC4, - .version_id = 1, - .minimum_version_id = 1, + .version_id = 2, + .minimum_version_id = 2, .fields = (const VMStateField[]) { VMSTATE_UINT32_ARRAY(mac_regs, DWMAC4State, DWMAC4_MAC_NR_REGS), VMSTATE_UINT32_ARRAY(dma_regs, DWMAC4State, DWMAC4_DMA_NR_REGS), - VMSTATE_UINT32_ARRAY(tx_desc_cur, DWMAC4State, DWMAC4_NR_CHANNELS), - VMSTATE_UINT32_ARRAY(rx_desc_cur, DWMAC4State, DWMAC4_NR_CHANNELS), + VMSTATE_UINT64_ARRAY(tx_desc_cur, DWMAC4State, DWMAC4_NR_CHANNELS), + VMSTATE_UINT64_ARRAY(rx_desc_cur, DWMAC4State, DWMAC4_NR_CHANNELS), VMSTATE_UINT16_ARRAY(phy_regs, DWMAC4State, 32), + VMSTATE_UINT16(phy_page, DWMAC4State), VMSTATE_END_OF_LIST(), }, }; static const Property dwmac4_properties[] = { DEFINE_NIC_PROPERTIES(DWMAC4State, conf), + DEFINE_PROP_UINT8("snps-version", DWMAC4State, snps_version, + DWMAC4_DEFAULT_SNPS_VERSION), + DEFINE_PROP_UINT8("user-version", DWMAC4State, user_version, 0), + DEFINE_PROP_UINT8("dma-width", DWMAC4State, dma_width, + DWMAC4_DEFAULT_DMA_WIDTH), + DEFINE_PROP_UINT8("phy-addr", DWMAC4State, phy_addr, 1), + DEFINE_PROP_UINT16("phy-id1", DWMAC4State, phy_id1, 0x001c), + DEFINE_PROP_UINT16("phy-id2", DWMAC4State, phy_id2, 0xc916), + DEFINE_PROP_UINT32("tx-fifo-size", DWMAC4State, tx_fifo_size, + DWMAC4_DEFAULT_FIFO_SIZE), + DEFINE_PROP_UINT32("rx-fifo-size", DWMAC4State, rx_fifo_size, + DWMAC4_DEFAULT_FIFO_SIZE), + DEFINE_PROP_BOOL("tso", DWMAC4State, tso, false), }; static void dwmac4_class_init(ObjectClass *klass, const void *data) diff --git a/hw/sd/Kconfig b/hw/sd/Kconfig index 4d98d664895fa..7850afe7f9d1b 100644 --- a/hw/sd/Kconfig +++ b/hw/sd/Kconfig @@ -43,3 +43,7 @@ config K230_SDHCI config SPACEMIT_K3_SDHCI bool select SDHCI + +config AX650X_SDHCI + bool + select SDHCI diff --git a/hw/sd/ax650x-sdhci.c b/hw/sd/ax650x-sdhci.c new file mode 100644 index 0000000000000..ae6dcbd4515d9 --- /dev/null +++ b/hw/sd/ax650x-sdhci.c @@ -0,0 +1,246 @@ +/* + * Axera AX650X SDHCI controller + * + * Copyright (c) 2026 Zevorn + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/core/qdev-properties.h" +#include "hw/sd/ax650x-sdhci.h" +#include "migration/vmstate.h" +#include "sdhci-internal.h" + +#define AX650X_VENDOR_ADDR(_addr) ((_addr) - AX650X_SDHCI_VENDOR_BASE) + +#define VREG(_name, _addr) \ + { .name = (_name), .addr = AX650X_VENDOR_ADDR(_addr) } +#define VREG_RO(_name, _addr, _reset) \ + { .name = (_name), .addr = AX650X_VENDOR_ADDR(_addr), \ + .reset = (_reset), .ro = UINT8_MAX } + +static uint64_t ax650x_register_bank_read(void *opaque, hwaddr addr, + unsigned int size) +{ + uint64_t value = 0; + + for (unsigned int i = 0; i < size; i++) { + value |= register_read_memory(opaque, addr + i, 1) << (i * 8); + } + + return value; +} + +static void ax650x_register_bank_write(void *opaque, hwaddr addr, + uint64_t value, unsigned int size) +{ + for (unsigned int i = 0; i < size; i++) { + register_write_memory(opaque, addr + i, value >> (i * 8), 1); + } +} + +static const MemoryRegionOps ax650x_register_bank_ops = { + .read = ax650x_register_bank_read, + .write = ax650x_register_bank_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static void ax650x_dll_ctrl_post_write(RegisterInfo *reg, uint64_t value) +{ + AX650XSDHCIState *s = AX650X_SDHCI(reg->opaque); + unsigned int status = AX650X_VENDOR_ADDR(AX650X_SDHCI_PHY_DLL_STATUS); + + if (value & AX650X_SDHCI_DLL_EN) { + s->vendor_regs[status] = AX650X_SDHCI_DLL_LOCKED; + } else { + s->vendor_regs[status] = 0; + } +} + +static void ax650x_emmc_ctrl_post_write(RegisterInfo *reg, uint64_t value) +{ + AX650XSDHCIState *s = AX650X_SDHCI(reg->opaque); + BusChild *child; + bool reset_asserted; + + reset_asserted = (value & AX650X_SDHCI_EMMC_RST_N_OE) && + !(value & AX650X_SDHCI_EMMC_RST_N); + if (s->emmc_reset_asserted && !reset_asserted) { + child = QTAILQ_FIRST(&s->sdhci.sdbus.qbus.children); + if (child) { + device_cold_reset(child->child); + } + } + s->emmc_reset_asserted = reset_asserted; +} + +static const RegisterAccessInfo ax650x_pointer_regs_info[] = { + { .name = "P_VENDOR_SPECIFIC_AREA[7:0]", .addr = 0, + .reset = AX650X_SDHCI_VENDOR_PTR_VALUE & 0xff, .ro = UINT8_MAX }, + { .name = "P_VENDOR_SPECIFIC_AREA[15:8]", .addr = 1, + .reset = AX650X_SDHCI_VENDOR_PTR_VALUE >> 8, .ro = UINT8_MAX }, +}; + +static const RegisterAccessInfo ax650x_vendor_regs_info[] = { + { .name = "PHY_CNFG[7:0]", + .addr = AX650X_VENDOR_ADDR(AX650X_SDHCI_PHY_CNFG), + .reset = AX650X_SDHCI_PHY_PWRGOOD, + .ro = AX650X_SDHCI_PHY_PWRGOOD }, + VREG("PHY_CNFG[15:8]", AX650X_SDHCI_PHY_CNFG + 1), + VREG("PHY_CNFG[23:16]", AX650X_SDHCI_PHY_CNFG + 2), + VREG("PHY_CNFG[31:24]", AX650X_SDHCI_PHY_CNFG + 3), + VREG("PHY_CMDPAD_CNFG[7:0]", AX650X_SDHCI_PHY_CMDPAD_CNFG), + VREG("PHY_CMDPAD_CNFG[15:8]", AX650X_SDHCI_PHY_CMDPAD_CNFG + 1), + VREG("PHY_DATAPAD_CNFG[7:0]", AX650X_SDHCI_PHY_DATAPAD_CNFG), + VREG("PHY_DATAPAD_CNFG[15:8]", AX650X_SDHCI_PHY_DATAPAD_CNFG + 1), + VREG("PHY_CLKPAD_CNFG[7:0]", AX650X_SDHCI_PHY_CLKPAD_CNFG), + VREG("PHY_CLKPAD_CNFG[15:8]", AX650X_SDHCI_PHY_CLKPAD_CNFG + 1), + VREG("PHY_STBPAD_CNFG[7:0]", AX650X_SDHCI_PHY_STBPAD_CNFG), + VREG("PHY_STBPAD_CNFG[15:8]", AX650X_SDHCI_PHY_STBPAD_CNFG + 1), + VREG("PHY_RSTNPAD_CNFG[7:0]", AX650X_SDHCI_PHY_RSTNPAD_CNFG), + VREG("PHY_RSTNPAD_CNFG[15:8]", AX650X_SDHCI_PHY_RSTNPAD_CNFG + 1), + VREG("PHY_COMMDL_CNFG", AX650X_SDHCI_PHY_COMMDL_CNFG), + VREG("PHY_SDCLKDL_CNFG", AX650X_SDHCI_PHY_SDCLKDL_CNFG), + VREG("PHY_SDCLKDL_DC", AX650X_SDHCI_PHY_SDCLKDL_DC), + VREG("PHY_SMPLDL_CNFG", AX650X_SDHCI_PHY_SMPLDL_CNFG), + VREG("PHY_ATDL_CNFG", AX650X_SDHCI_PHY_ATDL_CNFG), + { .name = "PHY_DLL_CTRL", + .addr = AX650X_VENDOR_ADDR(AX650X_SDHCI_PHY_DLL_CTRL), + .rsvd = 0xf8, + .post_write = ax650x_dll_ctrl_post_write }, + VREG("PHY_DLL_CNFG1", AX650X_SDHCI_PHY_DLL_CNFG1), + VREG("PHY_DLL_CNFG2", AX650X_SDHCI_PHY_DLL_CNFG2), + VREG("PHY_DLLDL_CNFG", AX650X_SDHCI_PHY_DLLDL_CNFG), + VREG("PHY_DLL_OFFSET", AX650X_SDHCI_PHY_DLL_OFFSET), + VREG("PHY_DLLLBT_CNFG[7:0]", AX650X_SDHCI_PHY_DLLLBT_CNFG), + VREG("PHY_DLLLBT_CNFG[15:8]", AX650X_SDHCI_PHY_DLLLBT_CNFG + 1), + VREG_RO("PHY_DLL_STATUS", AX650X_SDHCI_PHY_DLL_STATUS, 0), + VREG_RO("PHY_DLLDBG_MLKDC", AX650X_SDHCI_PHY_DLLDBG_MLKDC, 98), + VREG_RO("PHY_DLLDBG_SLKDC", AX650X_SDHCI_PHY_DLLDBG_SLKDC, 24), + { .name = "EMMC_CTRL[7:0]", + .addr = AX650X_VENDOR_ADDR(AX650X_SDHCI_EMMC_CTRL), + .rsvd = 0xf2, + .post_write = ax650x_emmc_ctrl_post_write }, + { .name = "EMMC_CTRL[15:8]", + .addr = AX650X_VENDOR_ADDR(AX650X_SDHCI_EMMC_CTRL + 1), + .rsvd = 0xfe }, +}; + +static void ax650x_sdhci_reset(DeviceState *dev) +{ + AX650XSDHCIState *s = AX650X_SDHCI(dev); + + device_cold_reset(DEVICE(&s->sdhci)); + for (unsigned int i = 0; + i < s->pointer_reg_array->num_elements; i++) { + register_reset(s->pointer_reg_array->r[i]); + } + for (unsigned int i = 0; + i < s->vendor_reg_array->num_elements; i++) { + register_reset(s->vendor_reg_array->r[i]); + } + s->emmc_reset_asserted = false; +} + +static void ax650x_sdhci_realize(DeviceState *dev, Error **errp) +{ + AX650XSDHCIState *s = AX650X_SDHCI(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + SysBusDevice *sdhci_sbd = SYS_BUS_DEVICE(&s->sdhci); + + qdev_prop_set_uint8(DEVICE(&s->sdhci), "sd-spec-version", 3); + qdev_prop_set_uint8(DEVICE(&s->sdhci), "uhs", UHS_I); + qdev_prop_set_uint64(DEVICE(&s->sdhci), "capareg", + SDHC_CAPAB_REG_DEFAULT | + R_SDHC_CAPAB_EMBEDDED_8BIT_MASK | + R_SDHC_CAPAB_BUS64BIT_MASK); + if (!sysbus_realize(sdhci_sbd, errp)) { + return; + } + + memory_region_init(&s->container, OBJECT(s), "ax650x.sdhci-container", + AX650X_SDHCI_REG_SIZE); + sysbus_init_mmio(sbd, &s->container); + memory_region_add_subregion(&s->container, 0, + sysbus_mmio_get_region(sdhci_sbd, 0)); + memory_region_add_subregion_overlap(&s->container, + AX650X_SDHCI_VENDOR_PTR, + &s->pointer_reg_array->mem, 1); + memory_region_add_subregion(&s->container, AX650X_SDHCI_VENDOR_BASE, + &s->vendor_reg_array->mem); + sysbus_pass_irq(sbd, sdhci_sbd); +} + +static const VMStateDescription vmstate_ax650x_sdhci = { + .name = TYPE_AX650X_SDHCI, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_UINT8_ARRAY(pointer_regs, AX650XSDHCIState, 2), + VMSTATE_UINT8_ARRAY(vendor_regs, AX650XSDHCIState, + AX650X_SDHCI_VENDOR_SIZE), + VMSTATE_BOOL(emmc_reset_asserted, AX650XSDHCIState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void ax650x_sdhci_init(Object *obj) +{ + AX650XSDHCIState *s = AX650X_SDHCI(obj); + + object_initialize_child(obj, "generic-sdhci", &s->sdhci, + TYPE_SYSBUS_SDHCI); + s->pointer_reg_array = + register_init_block8(DEVICE(obj), ax650x_pointer_regs_info, + ARRAY_SIZE(ax650x_pointer_regs_info), + s->pointer_regs_info, s->pointer_regs, + &ax650x_register_bank_ops, false, + sizeof(s->pointer_regs)); + s->vendor_reg_array = + register_init_block8(DEVICE(obj), ax650x_vendor_regs_info, + ARRAY_SIZE(ax650x_vendor_regs_info), + s->vendor_regs_info, s->vendor_regs, + &ax650x_register_bank_ops, false, + sizeof(s->vendor_regs)); +} + +static void ax650x_sdhci_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->desc = "Axera AX650X SD/eMMC Host Controller"; + dc->realize = ax650x_sdhci_realize; + device_class_set_legacy_reset(dc, ax650x_sdhci_reset); + dc->vmsd = &vmstate_ax650x_sdhci; +} + +SDBus *ax650x_sdhci_get_bus(AX650XSDHCIState *s) +{ + return &s->sdhci.sdbus; +} + +static const TypeInfo ax650x_sdhci_info = { + .name = TYPE_AX650X_SDHCI, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AX650XSDHCIState), + .instance_init = ax650x_sdhci_init, + .class_init = ax650x_sdhci_class_init, +}; + +static void ax650x_sdhci_register_types(void) +{ + type_register_static(&ax650x_sdhci_info); +} + +type_init(ax650x_sdhci_register_types) diff --git a/hw/sd/meson.build b/hw/sd/meson.build index b637c06c87eef..bf3ec8b98458d 100644 --- a/hw/sd/meson.build +++ b/hw/sd/meson.build @@ -15,3 +15,4 @@ system_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_sdhci.c')) system_ss.add(when: 'CONFIG_CADENCE_SDHCI', if_true: files('cadence_sdhci.c')) system_ss.add(when: 'CONFIG_K230_SDHCI', if_true: files('k230_sdhci.c')) system_ss.add(when: 'CONFIG_SPACEMIT_K3_SDHCI', if_true: files('spacemit-k3-sdhci.c')) +system_ss.add(when: 'CONFIG_AX650X_SDHCI', if_true: files('ax650x-sdhci.c')) diff --git a/hw/sd/sdhci-internal.h b/hw/sd/sdhci-internal.h index b7457199c1edb..6bc6b48c4e659 100644 --- a/hw/sd/sdhci-internal.h +++ b/hw/sd/sdhci-internal.h @@ -46,7 +46,7 @@ #define SDHC_TRNS_ACMD23 0x0008 /* since v3 */ #define SDHC_TRNS_READ 0x0010 #define SDHC_TRNS_MULTI 0x0020 -#define SDHC_TRNMOD_MASK 0x0037 +#define SDHC_TRNMOD_MASK 0x003f /* R/W Command Register 0x0 */ #define SDHC_CMDREG 0x0E diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c index 76c94642227f8..67e7e600128d3 100644 --- a/hw/sd/sdhci.c +++ b/hw/sd/sdhci.c @@ -336,6 +336,30 @@ static void sdhci_data_transfer(void *opaque); #define BLOCK_SIZE_MASK (4 * KiB - 1) +static bool sdhci_send_auto_cmd23(SDHCIState *s) +{ + SDRequest request = { + .cmd = 23, + .arg = s->sdmasysad, + }; + uint8_t response[16]; + size_t rlen; + + trace_sdhci_send_command(request.cmd, request.arg); + rlen = sdbus_do_command(&s->sdbus, &request, response, sizeof(response)); + if (rlen == 4) { + return true; + } + + trace_sdhci_error("Auto CMD23 failed"); + s->acmd12errsts |= R_SDHC_ACMD12ERRSTS_TIMEOUT_ERR_MASK; + if (s->errintstsen & SDHC_EIS_CMD12ERR) { + s->errintsts |= SDHC_EIS_CMD12ERR; + s->norintsts |= SDHC_NIS_ERR; + } + return false; +} + static void sdhci_send_command(SDHCIState *s) { SDRequest request; @@ -348,6 +372,13 @@ static void sdhci_send_command(SDHCIState *s) request.cmd = s->cmdreg >> 8; request.arg = s->argument; + if ((s->trnmod & SDHC_TRNS_ACMD23) && + (s->cmdreg & SDHC_CMD_DATA_PRESENT) && + !sdhci_send_auto_cmd23(s)) { + sdhci_update_irq(s); + return; + } + trace_sdhci_send_command(request.cmd, request.arg); rlen = sdbus_do_command(&s->sdbus, &request, response, sizeof(response)); diff --git a/include/hw/arm/ax650x-dwmac.h b/include/hw/arm/ax650x-dwmac.h new file mode 100644 index 0000000000000..47fa262dc4057 --- /dev/null +++ b/include/hw/arm/ax650x-dwmac.h @@ -0,0 +1,15 @@ +/* + * AX650X DWMAC board integration + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_ARM_AX650X_DWMAC_H +#define HW_ARM_AX650X_DWMAC_H + +#include "hw/core/qdev.h" + +void ax650x_dwmac_create(DeviceState *gic); +void ax650x_dwmac_create_fdt(void *fdt); + +#endif /* HW_ARM_AX650X_DWMAC_H */ diff --git a/include/hw/gpio/dw-apb-gpio.h b/include/hw/gpio/dw-apb-gpio.h new file mode 100644 index 0000000000000..f3740f3b5c6c6 --- /dev/null +++ b/include/hw/gpio/dw-apb-gpio.h @@ -0,0 +1,32 @@ +/* + * Synopsys DesignWare APB GPIO + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_GPIO_DW_APB_GPIO_H +#define HW_GPIO_DW_APB_GPIO_H + +#include "hw/core/irq.h" +#include "hw/core/register.h" +#include "hw/core/sysbus.h" + +#define TYPE_DW_APB_GPIO "dw-apb-gpio" +OBJECT_DECLARE_SIMPLE_TYPE(DWAPBGPIOState, DW_APB_GPIO) + +#define DW_APB_GPIO_NR_PINS 32 +#define DW_APB_GPIO_REG_SIZE 0x54 +#define DW_APB_GPIO_NR_REGS (DW_APB_GPIO_REG_SIZE / sizeof(uint32_t)) +#define DW_APB_GPIO_MMIO_SIZE 0x400 + +typedef struct DWAPBGPIOState { + SysBusDevice parent; + + uint32_t regs[DW_APB_GPIO_NR_REGS]; + RegisterInfo regs_info[DW_APB_GPIO_NR_REGS]; + RegisterInfoArray *reg_array; + qemu_irq output[DW_APB_GPIO_NR_PINS]; + uint32_t input; +} DWAPBGPIOState; + +#endif /* HW_GPIO_DW_APB_GPIO_H */ diff --git a/include/hw/misc/ax650x-dwmac-glue.h b/include/hw/misc/ax650x-dwmac-glue.h new file mode 100644 index 0000000000000..d76705277dc68 --- /dev/null +++ b/include/hw/misc/ax650x-dwmac-glue.h @@ -0,0 +1,36 @@ +/* + * Axera AX650X DWMAC clock/reset/interface glue + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_AX650X_DWMAC_GLUE_H +#define HW_MISC_AX650X_DWMAC_GLUE_H + +#include "hw/core/register.h" +#include "hw/core/sysbus.h" + +#define TYPE_AX650X_DWMAC_GLUE "ax650x-dwmac-glue" +OBJECT_DECLARE_SIMPLE_TYPE(AX650XDWMACGlueState, AX650X_DWMAC_GLUE) + +#define AX650X_DWMAC_GLB_REG_SIZE 0xa0 +#define AX650X_DWMAC_GLB_NR_REGS \ + (AX650X_DWMAC_GLB_REG_SIZE / sizeof(uint32_t)) +#define AX650X_DWMAC_CLK_REG_SIZE 0x40 +#define AX650X_DWMAC_CLK_NR_REGS \ + (AX650X_DWMAC_CLK_REG_SIZE / sizeof(uint32_t)) +#define AX650X_DWMAC_GLUE_MMIO_SIZE 0x1000 + +typedef struct AX650XDWMACGlueState { + SysBusDevice parent; + + uint32_t glb_regs[AX650X_DWMAC_GLB_NR_REGS]; + RegisterInfo glb_regs_info[AX650X_DWMAC_GLB_NR_REGS]; + RegisterInfoArray *glb_reg_array; + uint32_t clk_regs[AX650X_DWMAC_CLK_NR_REGS]; + RegisterInfo clk_regs_info[AX650X_DWMAC_CLK_NR_REGS]; + RegisterInfoArray *clk_reg_array; + uint8_t port; +} AX650XDWMACGlueState; + +#endif /* HW_MISC_AX650X_DWMAC_GLUE_H */ diff --git a/include/hw/misc/ax650x-hwspinlock.h b/include/hw/misc/ax650x-hwspinlock.h new file mode 100644 index 0000000000000..1224091d06d35 --- /dev/null +++ b/include/hw/misc/ax650x-hwspinlock.h @@ -0,0 +1,25 @@ +/* + * AXERA AX650X hardware spinlock + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_AX650X_HWSPINLOCK_H +#define HW_MISC_AX650X_HWSPINLOCK_H + +#include "hw/core/sysbus.h" + +#define TYPE_AX650X_HWSPINLOCK "ax650x-hwspinlock" +OBJECT_DECLARE_SIMPLE_TYPE(AX650XHWSpinlockState, AX650X_HWSPINLOCK) + +#define AX650X_HWSPINLOCK_MMIO_SIZE 0x1000 +#define AX650X_HWSPINLOCK_COUNT 32 + +typedef struct AX650XHWSpinlockState { + SysBusDevice parent; + + MemoryRegion iomem; + uint32_t locked; +} AX650XHWSpinlockState; + +#endif /* HW_MISC_AX650X_HWSPINLOCK_H */ diff --git a/include/hw/net/dwmac4.h b/include/hw/net/dwmac4.h index 63fe4b477eb2f..817dbfe83b6bf 100644 --- a/include/hw/net/dwmac4.h +++ b/include/hw/net/dwmac4.h @@ -21,13 +21,13 @@ #include "net/net.h" /* - * MAC_VERSION (GMAC4_VERSION @ 0x110) synth-id. Linux stmmac reads SNPSVER - * (bits[7:0]) and routes through stmmac_hw[]: min_id=DWMAC_CORE_5_10 (0x51) - * selects dwmac510_ops / dwmac410_dma_ops / dwmac4_desc_ops. Real RK3588 - * silicon reports 0x51; the contract pins the model to 0x51. + * MAC_VERSION (GMAC4_VERSION @ 0x110) contains the Synopsys version in + * bits[7:0] and a user version in bits[15:8]. Boards select their reported + * version through properties; the defaults retain the original 0x51 model. */ -#define DWMAC4_SNPSVER 0x51u -#define DWMAC4_VERSION_RESET (DWMAC4_SNPSVER & 0xff) +#define DWMAC4_DEFAULT_SNPS_VERSION 0x51u +#define DWMAC4_DEFAULT_DMA_WIDTH 32u +#define DWMAC4_DEFAULT_FIFO_SIZE 4096u /* MAC register bank window. Covers 0x000..0x3ff (some slots beyond 0x300). */ #define DWMAC4_MAC_REG_SIZE 0x400 @@ -66,11 +66,23 @@ typedef struct DWMAC4State { RegisterInfoArray *dma_reg_array; /* Per-channel descriptor-ring cursor state (not part of the reg bank). */ - uint32_t tx_desc_cur[DWMAC4_NR_CHANNELS]; - uint32_t rx_desc_cur[DWMAC4_NR_CHANNELS]; + uint64_t tx_desc_cur[DWMAC4_NR_CHANNELS]; + uint64_t rx_desc_cur[DWMAC4_NR_CHANNELS]; /* MDIO clause-22 PHY scratch (minimal: link up, full-duplex 1G). */ uint16_t phy_regs[32]; + uint16_t phy_page; + + /* Board-selectable synthesis and PHY identity. */ + uint8_t snps_version; + uint8_t user_version; + uint8_t dma_width; + uint8_t phy_addr; + uint16_t phy_id1; + uint16_t phy_id2; + uint32_t tx_fifo_size; + uint32_t rx_fifo_size; + bool tso; } DWMAC4State; #define TYPE_DWMAC4 "dwmac4" diff --git a/include/hw/sd/ax650x-sdhci.h b/include/hw/sd/ax650x-sdhci.h new file mode 100644 index 0000000000000..be4b5083186b5 --- /dev/null +++ b/include/hw/sd/ax650x-sdhci.h @@ -0,0 +1,75 @@ +/* + * Axera AX650X SDHCI controller + * + * Copyright (c) 2026 Zevorn + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_SD_AX650X_SDHCI_H +#define HW_SD_AX650X_SDHCI_H + +#include "hw/core/register.h" +#include "hw/sd/sdhci.h" + +#define TYPE_AX650X_SDHCI "ax650x-sdhci" +OBJECT_DECLARE_SIMPLE_TYPE(AX650XSDHCIState, AX650X_SDHCI) + +#define AX650X_SDHCI_REG_SIZE 0x600 +#define AX650X_SDHCI_VENDOR_BASE 0x100 +#define AX650X_SDHCI_VENDOR_SIZE 0x500 +#define AX650X_SDHCI_VENDOR_PTR 0x0e8 +#define AX650X_SDHCI_VENDOR_PTR_VALUE 0x500 + +#define AX650X_SDHCI_PHY_CNFG 0x300 +#define AX650X_SDHCI_PHY_CMDPAD_CNFG 0x304 +#define AX650X_SDHCI_PHY_DATAPAD_CNFG 0x306 +#define AX650X_SDHCI_PHY_CLKPAD_CNFG 0x308 +#define AX650X_SDHCI_PHY_STBPAD_CNFG 0x30a +#define AX650X_SDHCI_PHY_RSTNPAD_CNFG 0x30c +#define AX650X_SDHCI_PHY_COMMDL_CNFG 0x31c +#define AX650X_SDHCI_PHY_SDCLKDL_CNFG 0x31d +#define AX650X_SDHCI_PHY_SDCLKDL_DC 0x31e +#define AX650X_SDHCI_PHY_SMPLDL_CNFG 0x320 +#define AX650X_SDHCI_PHY_ATDL_CNFG 0x321 +#define AX650X_SDHCI_PHY_DLL_CTRL 0x324 +#define AX650X_SDHCI_PHY_DLL_CNFG1 0x325 +#define AX650X_SDHCI_PHY_DLL_CNFG2 0x326 +#define AX650X_SDHCI_PHY_DLLDL_CNFG 0x328 +#define AX650X_SDHCI_PHY_DLL_OFFSET 0x329 +#define AX650X_SDHCI_PHY_DLLLBT_CNFG 0x32c +#define AX650X_SDHCI_PHY_DLL_STATUS 0x32e +#define AX650X_SDHCI_PHY_DLLDBG_MLKDC 0x330 +#define AX650X_SDHCI_PHY_DLLDBG_SLKDC 0x332 +#define AX650X_SDHCI_EMMC_CTRL 0x52c + +#define AX650X_SDHCI_PHY_PWRGOOD BIT(1) +#define AX650X_SDHCI_PHY_RSTN BIT(0) +#define AX650X_SDHCI_DLL_EN BIT(0) +#define AX650X_SDHCI_DLL_ERROR BIT(1) +#define AX650X_SDHCI_DLL_LOCKED BIT(0) +#define AX650X_SDHCI_CARD_IS_EMMC BIT(0) +#define AX650X_SDHCI_EMMC_RST_N BIT(2) +#define AX650X_SDHCI_EMMC_RST_N_OE BIT(3) +#define AX650X_SDHCI_ENH_STROBE_EN BIT(8) + +struct AX650XSDHCIState { + SysBusDevice parent_obj; + + MemoryRegion container; + SDHCIState sdhci; + + RegisterInfo pointer_regs_info[2]; + uint8_t pointer_regs[2]; + RegisterInfoArray *pointer_reg_array; + + RegisterInfo vendor_regs_info[AX650X_SDHCI_VENDOR_SIZE]; + uint8_t vendor_regs[AX650X_SDHCI_VENDOR_SIZE]; + RegisterInfoArray *vendor_reg_array; + + bool emmc_reset_asserted; +}; + +SDBus *ax650x_sdhci_get_bus(AX650XSDHCIState *s); + +#endif diff --git a/tests/functional/aarch64/meson.build b/tests/functional/aarch64/meson.build index d5f3246ad63ab..23a4875dce255 100644 --- a/tests/functional/aarch64/meson.build +++ b/tests/functional/aarch64/meson.build @@ -4,6 +4,7 @@ test_aarch64_timeouts = { 'aspeed_ast2700a1' : 600, 'aspeed_ast2700a2' : 600, 'aspeed_ast2700fc' : 600, + 'ax650x_ubuntu' : 180, 'device_passthrough' : 720, 'imx8mm_evk' : 240, 'imx8mp_evk' : 240, @@ -31,6 +32,7 @@ tests_aarch64_system_thorough = [ 'aspeed_ast2700a1', 'aspeed_ast2700a2', 'aspeed_ast2700fc', + 'ax650x_ubuntu', 'device_passthrough', 'hotplug_pci', 'imx8mm_evk', diff --git a/tests/functional/aarch64/test_ax650x_ubuntu.py b/tests/functional/aarch64/test_ax650x_ubuntu.py new file mode 100644 index 0000000000000..67c61856dbd76 --- /dev/null +++ b/tests/functional/aarch64/test_ax650x_ubuntu.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# +# Functional test that directly boots an AX650X Ubuntu eMMC image +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset, LinuxKernelTest + + +class AX650XUbuntuMachine(LinuxKernelTest): + + ASSET_URL = ( + 'https://github.com/processmission/qemu/releases/download/' + 'ax650x-ubuntu-22.04-qemu1/' + ) + ASSET_KERNEL = Asset( + ASSET_URL + 'Image-5.15.73-axera', + '9e3dc56e13cb5e786864d3456ceecfe7560d11fd25e9688531f86c0748be9bf8') + ASSET_EMMC = Asset( + ASSET_URL + 'ax650x-ubuntu-22.04-emmc.qcow2', + 'e362cff67d7358bc434394649f91d0b2424156eb9cd3b518438cf32fe3b9800c') + + PARTS = ( + 'mmcblk0:1536K(uboot),1536K(uboot_bk),1M(env),20M(param),' + '6M(logo),1M(dtb),64M(kernel),1M(atf),1M(optee),' + '1M(recovery_dtb),74M(recovery),30380032K(rootfs)' + ) + + def test_aarch64_ax650x_ubuntu_quick_boot(self): + kernel_path = self.ASSET_KERNEL.fetch() + image_path = self.ASSET_EMMC.fetch() + + self.set_machine('ax650x-pyramid') + self.require_accelerator('tcg') + self.vm.set_console() + + kernel_command_line = ( + 'console=ttyS0,115200n8 ' + 'earlycon=uart8250,mmio32,0x2016000 ' + 'root=/dev/mmcblk0p12 rootfstype=ext4 rw rootwait ' + f'blkdevparts={self.PARTS} ' + 'systemd.show_status=yes systemd.log_target=console' + ) + self.vm.add_args( + '-accel', 'tcg,thread=multi', + '-cpu', 'cortex-a55', + '-smp', '8', + '-m', '2G', + '-kernel', kernel_path, + '-append', kernel_command_line, + '-drive', f'file={image_path},if=sd,format=qcow2,snapshot=on', + '-display', 'none', + '-monitor', 'none', + '-no-reboot', + ) + + self.vm.launch() + self.wait_for_console_pattern('DWMAC4/5') + self.wait_for_console_pattern('p12(rootfs)') + self.wait_for_console_pattern( + 'Mounted root (ext4 filesystem) on device 179:12') + self.wait_for_console_pattern('AX650X_UBUNTU_READY') + self.wait_for_console_pattern('AX650X_OS=Ubuntu 22.04.5 LTS') + self.wait_for_console_pattern('AX650X_CPUS=8') + self.wait_for_console_pattern('AX650X_ROOT=/dev/mmcblk0p12 ext4') + self.wait_for_console_pattern('localhost login:') + + +if __name__ == '__main__': + LinuxKernelTest.main() diff --git a/tests/qtest/ax650x-pyramid-test.c b/tests/qtest/ax650x-pyramid-test.c new file mode 100644 index 0000000000000..3043d03c8d28e --- /dev/null +++ b/tests/qtest/ax650x-pyramid-test.c @@ -0,0 +1,628 @@ +/* + * QTest for the M5Stack AI Pyramid (Axera AX650X) + * + * Copyright (c) 2026 Zevorn + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "hw/sd/ax650x-sdhci.h" +#include "qobject/qdict.h" +#include "qobject/qlist.h" +#include "qobject/qnum.h" +#include "libqtest.h" +#include "libqos/sdhci-cmd.h" + +#define AX650X_NUM_CPUS 8 + +#define AX650X_RAM_BASE 0x100000000ULL +#define AX650X_RESET_RAM_TEST_ADDR (AX650X_RAM_BASE + 256 * MiB) + +#define AX650X_GIC_DIST_BASE 0x04901000 +#define AX650X_GIC_HYP_BASE 0x04904000 +#define AX650X_GICD_CTLR 0x000 +#define AX650X_GICD_TYPER 0x004 +#define AX650X_GICD_ISPENDR 0x200 +#define AX650X_GICH_VTR 0x004 + +#define AX650X_UART0_BASE 0x02016000 +#define AX650X_UART_REGSHIFT 2 +#define AX650X_UART_IER (1 << AX650X_UART_REGSHIFT) +#define AX650X_UART_LSR (5 << AX650X_UART_REGSHIFT) +#define AX650X_UART_USR 0x7c +#define AX650X_UART_UCV 0xf8 +#define AX650X_UART_IER_THRI 0x02 +#define AX650X_UART_LSR_THRE 0x20 +#define AX650X_UART_LSR_TEMT 0x40 +#define AX650X_UART_USR_TFNF BIT(1) +#define AX650X_UART_USR_TFE BIT(2) +#define AX650X_UART0_INTID (32 + 135) + +#define AX650X_EMMC_BASE 0x28000000 +#define AX650X_EMMC_INTID (32 + 93) +#define AX650X_EMMC_TEST_IMAGE_SIZE (1 * MiB) +#define AX650X_EMMC_BLOCK_SIZE 512 + +#define AX650X_DWMAC0_BASE 0x10140000 +#define AX650X_DWMAC1_BASE 0x30800000 +#define AX650X_DWMAC0_INTID (32 + 104) +#define AX650X_DWMAC0_GLB_BASE 0x10000000 +#define AX650X_DWMAC0_CLK_BASE 0x10010000 +#define AX650X_GPIO0_BASE 0x02003000 +#define AX650X_GPIO1_BASE 0x02004000 +#define AX650X_HWSPINLOCK_BASE 0x04510000 +#define AX650X_HWSPINLOCK_STRIDE 8 + +#define DWMAC_GMAC_VERSION 0x110 +#define DWMAC_GMAC_HW_FEATURE1 0x120 +#define DWMAC_GMAC_MDIO_ADDR 0x200 +#define DWMAC_GMAC_MDIO_DATA 0x204 +#define DWMAC_GMAC_VERSION_AX650X 0x1052 +#define DWMAC_HW_FEATURE1_ADDR64 (0x3 << 14) +#define DWMAC_HW_FEATURE1_ADDR64_40 BIT(14) +#define DWMAC_HW_FEATURE1_TSO BIT(18) +#define DWMAC_HW_FEATURE1_TXFIFO (0x1f << 6) +#define DWMAC_HW_FEATURE1_RXFIFO 0x1f +#define DWMAC_HW_FEATURE1_TXFIFO_32K (8 << 6) +#define DWMAC_HW_FEATURE1_RXFIFO_64K 9 +#define DWMAC_MDIO_BUSY BIT(0) +#define DWMAC_MDIO_GOC_WRITE (1 << 2) +#define DWMAC_MDIO_GOC_READ (3 << 2) +#define DWMAC_MDIO_REG(reg) ((reg) << 16) +#define DWMAC_MDIO_PHY(addr) ((addr) << 21) +#define DWMAC_BMCR_ANRESTART BIT(9) +#define DWMAC_BMCR_ANENABLE BIT(12) +#define DWMAC_BMCR_RESET BIT(15) + +#define DWMAC_DMA_CHAN_TX_CONTROL 0x1104 +#define DWMAC_DMA_CHAN_TX_BASE_HI 0x1110 +#define DWMAC_DMA_CHAN_TX_BASE 0x1114 +#define DWMAC_DMA_CHAN_TX_END_ADDR 0x1120 +#define DWMAC_DMA_CHAN_TX_RING_LEN 0x112c +#define DWMAC_DMA_CHAN_INTR_ENA 0x1134 +#define DWMAC_DMA_CHAN_STATUS 0x1160 +#define DWMAC_DMA_TX_START BIT(0) +#define DWMAC_DMA_STATUS_TI BIT(0) +#define DWMAC_DMA_STATUS_NIS BIT(15) + +#define DWMAC_DESC_OWN BIT(31) +#define DWMAC_TX_DESC_FIRST BIT(29) +#define DWMAC_TX_DESC_LAST BIT(28) +#define DWMAC_TX_DESC_IOC BIT(31) + +#define DW_GPIO_SWPORTA_DR 0x00 +#define DW_GPIO_SWPORTA_DDR 0x04 +#define DW_GPIO_EXT_PORTA 0x50 + +#define AX650X_DWMAC_GLB_PHY_IF 0x9c +#define AX650X_DWMAC_CLK_SW_RESET 0x10 +#define AX650X_DWMAC_CLK_RESET_SET 0x38 +#define AX650X_DWMAC_CLK_RESET_CLEAR 0x3c + +#define SDHC_NORINTSTS 0x30 +#define SDHC_NORINTSTSEN 0x34 +#define SDHC_NORINTSIGEN 0x38 +#define SDHC_ARGUMENT2 0x00 +#define SDHC_TRNS_AUTO_CMD23 0x0008 +#define SDHC_NIS_CMDCMP BIT(0) +#define SDHC_AX650X_HCVER 0x2402 +#define SDHC_EMMC_SEND_OP_COND (1 << 8) +#define SDHC_EMMC_SET_RELATIVE_ADDR (3 << 8) +#define SDHC_EMMC_SEND_STATUS (13 << 8) +#define SDHC_R1_READY_FOR_DATA BIT(8) +#define SDHC_R1_CURRENT_STATE_MASK 0x1e00 +#define SDHC_R1_STATE_TRAN (4 << 9) + +static char *emmc_path; + +static QTestState *ax650x_pyramid_start(void) +{ + return qtest_init("-machine ax650x-pyramid -accel qtest -display none"); +} + +static uint64_t qom_get_uint(QTestState *qts, const char *path, + const char *property) +{ + QDict *response; + QNum *number; + uint64_t value; + + response = qtest_qmp(qts, + "{ 'execute': 'qom-get'," + " 'arguments': { 'path': %s, 'property': %s } }", + path, property); + g_assert(qdict_haskey(response, "return")); + number = qobject_to(QNum, qdict_get(response, "return")); + g_assert_nonnull(number); + g_assert_true(qnum_get_try_uint(number, &value)); + qobject_unref(response); + + return value; +} + +static void test_cpu_topology(void) +{ + QTestState *qts = ax650x_pyramid_start(); + QDict *response; + QList *cpus; + QListEntry *entry; + + response = qtest_qmp(qts, "{ 'execute': 'query-cpus-fast' }"); + g_assert(qdict_haskey(response, "return")); + cpus = qdict_get_qlist(response, "return"); + g_assert_cmpuint(qlist_size(cpus), ==, AX650X_NUM_CPUS); + + QLIST_FOREACH_ENTRY(cpus, entry) { + QDict *cpu = qobject_to(QDict, qlist_entry_obj(entry)); + unsigned int index = qdict_get_int(cpu, "cpu-index"); + const char *path = qdict_get_str(cpu, "qom-path"); + + g_assert_cmpuint(index, <, AX650X_NUM_CPUS); + g_assert_cmphex(qom_get_uint(qts, path, "mp-affinity"), ==, + (uint64_t)index << 8); + } + + qobject_unref(response); + qtest_quit(qts); +} + +static void test_memory_and_gic(void) +{ + QTestState *qts = ax650x_pyramid_start(); + uint64_t pattern = 0x0123456789abcdefULL; + uint32_t typer; + uint32_t vtr; + + qtest_writeq(qts, AX650X_RAM_BASE + 0x1000, pattern); + g_assert_cmphex(qtest_readq(qts, AX650X_RAM_BASE + 0x1000), ==, + pattern); + + g_assert_cmphex(qtest_readl(qts, AX650X_GIC_DIST_BASE + AX650X_GICD_CTLR), + ==, 0); + typer = qtest_readl(qts, AX650X_GIC_DIST_BASE + AX650X_GICD_TYPER); + g_assert_cmpuint(typer & 0x1f, ==, 7); + g_assert_cmpuint((typer >> 5) & 0x7, ==, AX650X_NUM_CPUS - 1); + + vtr = qtest_readl(qts, AX650X_GIC_HYP_BASE + AX650X_GICH_VTR); + g_assert_cmpuint(vtr & 0x3f, ==, 3); + + qtest_quit(qts); +} + +static void test_uart_irq_and_reset(void) +{ + QTestState *qts = ax650x_pyramid_start(); + uint64_t ram_pattern = 0xfedcba9876543210ULL; + uint64_t pending_addr; + uint32_t pending_mask; + uint32_t lsr; + + lsr = qtest_readl(qts, AX650X_UART0_BASE + AX650X_UART_LSR); + g_assert_cmphex(lsr & (AX650X_UART_LSR_THRE | AX650X_UART_LSR_TEMT), ==, + AX650X_UART_LSR_THRE | AX650X_UART_LSR_TEMT); + g_assert_cmphex(qtest_readl(qts, AX650X_UART0_BASE + AX650X_UART_IER), + ==, 0); + + qtest_writel(qts, AX650X_UART0_BASE + AX650X_UART_IER, + AX650X_UART_IER_THRI); + pending_addr = AX650X_GIC_DIST_BASE + AX650X_GICD_ISPENDR + + (AX650X_UART0_INTID / 32) * sizeof(uint32_t); + pending_mask = 1U << (AX650X_UART0_INTID % 32); + g_assert_cmphex(qtest_readl(qts, pending_addr) & pending_mask, ==, + pending_mask); + + qtest_writeq(qts, AX650X_RESET_RAM_TEST_ADDR, ram_pattern); + qtest_system_reset(qts); + g_assert_cmphex(qtest_readl(qts, AX650X_UART0_BASE + AX650X_UART_IER), + ==, 0); + g_assert_cmphex(qtest_readl(qts, AX650X_GIC_DIST_BASE + AX650X_GICD_CTLR), + ==, 0); + g_assert_cmphex(qtest_readq(qts, AX650X_RESET_RAM_TEST_ADDR), ==, + ram_pattern); + + qtest_quit(qts); +} + +static void test_uart_extension_registers(void) +{ + QTestState *qts = ax650x_pyramid_start(); + + g_assert_cmphex(qtest_readl(qts, AX650X_UART0_BASE + AX650X_UART_USR), + ==, AX650X_UART_USR_TFNF | AX650X_UART_USR_TFE); + g_assert_cmphex(qtest_readl(qts, AX650X_UART0_BASE + AX650X_UART_UCV), + ==, 0); + + qtest_quit(qts); +} + +static uint16_t dwmac_mdio_read(QTestState *qts, uint64_t base, + unsigned int phy, unsigned int reg) +{ + uint32_t command = DWMAC_MDIO_BUSY | DWMAC_MDIO_GOC_READ | + DWMAC_MDIO_PHY(phy) | DWMAC_MDIO_REG(reg); + + qtest_writel(qts, base + DWMAC_GMAC_MDIO_ADDR, command); + g_assert_cmphex(qtest_readl(qts, base + DWMAC_GMAC_MDIO_ADDR) & + DWMAC_MDIO_BUSY, ==, 0); + return qtest_readl(qts, base + DWMAC_GMAC_MDIO_DATA); +} + +static void dwmac_mdio_write(QTestState *qts, uint64_t base, + unsigned int phy, unsigned int reg, + uint16_t value) +{ + uint32_t command = DWMAC_MDIO_BUSY | DWMAC_MDIO_GOC_WRITE | + DWMAC_MDIO_PHY(phy) | DWMAC_MDIO_REG(reg); + + qtest_writel(qts, base + DWMAC_GMAC_MDIO_DATA, value); + qtest_writel(qts, base + DWMAC_GMAC_MDIO_ADDR, command); + g_assert_cmphex(qtest_readl(qts, base + DWMAC_GMAC_MDIO_ADDR) & + DWMAC_MDIO_BUSY, ==, 0); +} + +static void test_dwmac_registers_glue_and_reset(void) +{ + QTestState *qts = ax650x_pyramid_start(); + uint32_t feature; + + g_assert_cmphex(qtest_readl(qts, AX650X_DWMAC0_BASE + + DWMAC_GMAC_VERSION), ==, + DWMAC_GMAC_VERSION_AX650X); + g_assert_cmphex(qtest_readl(qts, AX650X_DWMAC1_BASE + + DWMAC_GMAC_VERSION), ==, + DWMAC_GMAC_VERSION_AX650X); + + feature = qtest_readl(qts, AX650X_DWMAC0_BASE + + DWMAC_GMAC_HW_FEATURE1); + g_assert_cmphex(feature & DWMAC_HW_FEATURE1_ADDR64, ==, + DWMAC_HW_FEATURE1_ADDR64_40); + g_assert_cmphex(feature & DWMAC_HW_FEATURE1_TSO, ==, + 0); + g_assert_cmphex(feature & DWMAC_HW_FEATURE1_TXFIFO, ==, + DWMAC_HW_FEATURE1_TXFIFO_32K); + g_assert_cmphex(feature & DWMAC_HW_FEATURE1_RXFIFO, ==, + DWMAC_HW_FEATURE1_RXFIFO_64K); + + g_assert_cmphex(dwmac_mdio_read(qts, AX650X_DWMAC0_BASE, 1, 2), ==, + 0x937c); + g_assert_cmphex(dwmac_mdio_read(qts, AX650X_DWMAC0_BASE, 1, 3), ==, + 0x4030); + g_assert_cmphex(dwmac_mdio_read(qts, AX650X_DWMAC0_BASE, 2, 2), ==, + 0xffff); + dwmac_mdio_write(qts, AX650X_DWMAC0_BASE, 1, 0, + DWMAC_BMCR_RESET | DWMAC_BMCR_ANENABLE | + DWMAC_BMCR_ANRESTART); + g_assert_cmphex(dwmac_mdio_read(qts, AX650X_DWMAC0_BASE, 1, 0), ==, + DWMAC_BMCR_ANENABLE); + + qtest_writel(qts, AX650X_GPIO0_BASE + DW_GPIO_SWPORTA_DDR, BIT(7)); + qtest_writel(qts, AX650X_GPIO0_BASE + DW_GPIO_SWPORTA_DR, BIT(7)); + g_assert_cmphex(qtest_readl(qts, AX650X_GPIO0_BASE + DW_GPIO_EXT_PORTA), + ==, BIT(7)); + qtest_writel(qts, AX650X_GPIO1_BASE + DW_GPIO_SWPORTA_DDR, BIT(11)); + qtest_writel(qts, AX650X_GPIO1_BASE + DW_GPIO_SWPORTA_DR, BIT(11)); + g_assert_cmphex(qtest_readl(qts, AX650X_GPIO1_BASE + DW_GPIO_EXT_PORTA), + ==, BIT(11)); + + qtest_writel(qts, AX650X_DWMAC0_GLB_BASE + AX650X_DWMAC_GLB_PHY_IF, + BIT(4)); + g_assert_cmphex(qtest_readl(qts, AX650X_DWMAC0_GLB_BASE + + AX650X_DWMAC_GLB_PHY_IF), ==, BIT(4)); + qtest_writel(qts, AX650X_DWMAC0_CLK_BASE + + AX650X_DWMAC_CLK_RESET_SET, BIT(7)); + g_assert_cmphex(qtest_readl(qts, AX650X_DWMAC0_CLK_BASE + + AX650X_DWMAC_CLK_SW_RESET), ==, BIT(7)); + qtest_writel(qts, AX650X_DWMAC0_CLK_BASE + + AX650X_DWMAC_CLK_RESET_CLEAR, BIT(7)); + g_assert_cmphex(qtest_readl(qts, AX650X_DWMAC0_CLK_BASE + + AX650X_DWMAC_CLK_SW_RESET), ==, 0); + + g_assert_cmphex(qtest_readl(qts, AX650X_HWSPINLOCK_BASE + + 12 * AX650X_HWSPINLOCK_STRIDE), ==, 0); + g_assert_cmphex(qtest_readl(qts, AX650X_HWSPINLOCK_BASE + + 12 * AX650X_HWSPINLOCK_STRIDE), ==, 1); + qtest_writel(qts, AX650X_HWSPINLOCK_BASE + + 12 * AX650X_HWSPINLOCK_STRIDE + sizeof(uint32_t), + 0xffffffff); + g_assert_cmphex(qtest_readl(qts, AX650X_HWSPINLOCK_BASE + + 12 * AX650X_HWSPINLOCK_STRIDE), ==, 0); + + qtest_system_reset(qts); + g_assert_cmphex(qtest_readl(qts, AX650X_DWMAC0_BASE + + DWMAC_GMAC_VERSION), ==, + DWMAC_GMAC_VERSION_AX650X); + g_assert_cmphex(qtest_readl(qts, AX650X_GPIO0_BASE + + DW_GPIO_EXT_PORTA), ==, 0); + g_assert_cmphex(qtest_readl(qts, AX650X_DWMAC0_GLB_BASE + + AX650X_DWMAC_GLB_PHY_IF), ==, 0); + + qtest_quit(qts); +} + +static void test_dwmac_40bit_dma_and_irq(void) +{ + const uint64_t desc_addr = AX650X_RAM_BASE + 2 * MiB; + const uint64_t frame_addr = desc_addr + 0x1000; + uint8_t frame[64] = { + 0x52, 0x54, 0x00, 0x12, 0x34, 0x56, + 0x52, 0x54, 0x00, 0x65, 0x43, 0x21, + 0x08, 0x00, + }; + uint32_t desc[4] = { + cpu_to_le32((uint32_t)frame_addr), + cpu_to_le32(frame_addr >> 32), + cpu_to_le32(DWMAC_TX_DESC_IOC | sizeof(frame)), + cpu_to_le32(DWMAC_DESC_OWN | DWMAC_TX_DESC_FIRST | + DWMAC_TX_DESC_LAST), + }; + QTestState *qts = ax650x_pyramid_start(); + uint64_t pending_addr; + uint32_t pending_mask; + uint32_t status; + + qtest_memwrite(qts, frame_addr, frame, sizeof(frame)); + qtest_memwrite(qts, desc_addr, desc, sizeof(desc)); + + qtest_writel(qts, AX650X_DWMAC0_BASE + DWMAC_DMA_CHAN_INTR_ENA, + DWMAC_DMA_STATUS_TI | DWMAC_DMA_STATUS_NIS); + qtest_writel(qts, AX650X_DWMAC0_BASE + DWMAC_DMA_CHAN_TX_BASE_HI, + desc_addr >> 32); + qtest_writel(qts, AX650X_DWMAC0_BASE + DWMAC_DMA_CHAN_TX_BASE, + (uint32_t)desc_addr); + qtest_writel(qts, AX650X_DWMAC0_BASE + DWMAC_DMA_CHAN_TX_RING_LEN, 0); + qtest_writel(qts, AX650X_DWMAC0_BASE + DWMAC_DMA_CHAN_TX_CONTROL, + DWMAC_DMA_TX_START); + qtest_writel(qts, AX650X_DWMAC0_BASE + DWMAC_DMA_CHAN_TX_END_ADDR, + (uint32_t)desc_addr); + + qtest_memread(qts, desc_addr, desc, sizeof(desc)); + g_assert_cmphex(le32_to_cpu(desc[3]) & DWMAC_DESC_OWN, ==, 0); + status = qtest_readl(qts, AX650X_DWMAC0_BASE + + DWMAC_DMA_CHAN_STATUS); + g_assert_cmphex(status & (DWMAC_DMA_STATUS_TI | DWMAC_DMA_STATUS_NIS), + ==, DWMAC_DMA_STATUS_TI | DWMAC_DMA_STATUS_NIS); + + pending_addr = AX650X_GIC_DIST_BASE + AX650X_GICD_ISPENDR + + (AX650X_DWMAC0_INTID / 32) * sizeof(uint32_t); + pending_mask = 1U << (AX650X_DWMAC0_INTID % 32); + g_assert_cmphex(qtest_readl(qts, pending_addr) & pending_mask, ==, + pending_mask); + + qtest_writel(qts, AX650X_DWMAC0_BASE + DWMAC_DMA_CHAN_STATUS, + DWMAC_DMA_STATUS_TI | DWMAC_DMA_STATUS_NIS); + g_assert_cmphex(qtest_readl(qts, AX650X_DWMAC0_BASE + + DWMAC_DMA_CHAN_STATUS) & + (DWMAC_DMA_STATUS_TI | DWMAC_DMA_STATUS_NIS), ==, 0); + + qtest_quit(qts); +} + +static void test_emmc_registers_and_reset(void) +{ + QTestState *qts = ax650x_pyramid_start(); + uint64_t base = AX650X_EMMC_BASE; + uint64_t capabilities; + + g_assert_cmphex(qtest_readw(qts, base + AX650X_SDHCI_VENDOR_PTR), ==, + AX650X_SDHCI_VENDOR_PTR_VALUE); + capabilities = qtest_readq(qts, base + SDHC_CAPAB); + g_assert_cmphex(capabilities & BIT_ULL(18), ==, BIT_ULL(18)); + g_assert_cmphex(capabilities & BIT_ULL(28), ==, BIT_ULL(28)); + g_assert_cmphex(qtest_readw(qts, base + SDHC_HCVER), ==, + SDHC_AX650X_HCVER); + + g_assert_cmphex(qtest_readl(qts, base + AX650X_SDHCI_PHY_CNFG), ==, + AX650X_SDHCI_PHY_PWRGOOD); + qtest_writel(qts, base + AX650X_SDHCI_PHY_CNFG, 0x00cc0001); + g_assert_cmphex(qtest_readl(qts, base + AX650X_SDHCI_PHY_CNFG), ==, + 0x00cc0003); + + qtest_writew(qts, base + AX650X_SDHCI_PHY_CMDPAD_CNFG, 0x0449); + g_assert_cmphex(qtest_readw(qts, + base + AX650X_SDHCI_PHY_CMDPAD_CNFG), ==, + 0x0449); + qtest_writeb(qts, base + AX650X_SDHCI_PHY_DLL_CTRL, + AX650X_SDHCI_DLL_EN); + g_assert_cmphex(qtest_readb(qts, + base + AX650X_SDHCI_PHY_DLL_STATUS), ==, + AX650X_SDHCI_DLL_LOCKED); + qtest_writeb(qts, base + AX650X_SDHCI_PHY_DLL_CTRL, 0); + g_assert_cmphex(qtest_readb(qts, + base + AX650X_SDHCI_PHY_DLL_STATUS), ==, 0); + + qtest_writeb(qts, base + AX650X_SDHCI_PHY_DLLDBG_MLKDC, 0xff); + qtest_writeb(qts, base + AX650X_SDHCI_PHY_DLLDBG_SLKDC, 0xff); + g_assert_cmphex(qtest_readb(qts, + base + AX650X_SDHCI_PHY_DLLDBG_MLKDC), ==, + 98); + g_assert_cmphex(qtest_readb(qts, + base + AX650X_SDHCI_PHY_DLLDBG_SLKDC), ==, + 24); + + qtest_writew(qts, base + AX650X_SDHCI_EMMC_CTRL, 0xffff); + g_assert_cmphex(qtest_readw(qts, base + AX650X_SDHCI_EMMC_CTRL), ==, + AX650X_SDHCI_CARD_IS_EMMC | + AX650X_SDHCI_EMMC_RST_N | + AX650X_SDHCI_EMMC_RST_N_OE | + AX650X_SDHCI_ENH_STROBE_EN); + + qtest_system_reset(qts); + g_assert_cmphex(qtest_readl(qts, base + AX650X_SDHCI_PHY_CNFG), ==, + AX650X_SDHCI_PHY_PWRGOOD); + g_assert_cmphex(qtest_readw(qts, + base + AX650X_SDHCI_PHY_CMDPAD_CNFG), ==, 0); + g_assert_cmphex(qtest_readb(qts, + base + AX650X_SDHCI_PHY_DLL_STATUS), ==, 0); + g_assert_cmphex(qtest_readw(qts, base + AX650X_SDHCI_EMMC_CTRL), ==, 0); + g_assert_cmphex(qtest_readw(qts, base + AX650X_SDHCI_VENDOR_PTR), ==, + AX650X_SDHCI_VENDOR_PTR_VALUE); + + qtest_quit(qts); +} + +static QTestState *ax650x_pyramid_start_with_emmc(void) +{ + QTestState *qts; + uint64_t base = AX650X_EMMC_BASE; + + qts = qtest_initf("-machine ax650x-pyramid -accel qtest -display none " + "-drive file=%s,if=sd,format=raw,auto-read-only=off", + emmc_path); + qtest_writeb(qts, base + SDHC_SWRST, SDHC_RESET_ALL); + qtest_writew(qts, base + SDHC_CLKCON, + SDHC_CLOCK_SDCLK_EN | SDHC_CLOCK_INT_EN); + + qtest_writel(qts, base + SDHC_NORINTSTSEN, SDHC_NIS_CMDCMP); + qtest_writel(qts, base + SDHC_NORINTSIGEN, SDHC_NIS_CMDCMP); + sdhci_cmd_regs(qts, base, 0, 0, 0, 0, 0); + + sdhci_cmd_regs(qts, base, 0, 0, 0x40ff8000, 0, + SDHC_EMMC_SEND_OP_COND); + sdhci_cmd_regs(qts, base, 0, 0, 0, 0, SDHC_ALL_SEND_CID); + sdhci_cmd_regs(qts, base, 0, 0, 1 << 16, 0, + SDHC_EMMC_SET_RELATIVE_ADDR | SDHC_CMD_RESPONSE); + sdhci_cmd_regs(qts, base, 0, 0, 1 << 16, 0, + SDHC_SELECT_DESELECT_CARD); + + return qts; +} + +static void test_emmc_block_io_and_irq(void) +{ + uint8_t source[AX650X_EMMC_BLOCK_SIZE]; + uint8_t written[AX650X_EMMC_BLOCK_SIZE]; + uint8_t readback[AX650X_EMMC_BLOCK_SIZE]; + QTestState *qts; + uint64_t pending_addr; + uint32_t pending_mask; + int fd; + ssize_t ret; + + for (unsigned int i = 0; i < sizeof(source); i++) { + source[i] = i % 127 + 1; + written[i] = (i * 3) % 127 + 1; + } + fd = open(emmc_path, O_WRONLY); + g_assert_cmpint(fd, >=, 0); + ret = pwrite(fd, source, sizeof(source), 0); + g_assert_cmpint(ret, ==, sizeof(source)); + close(fd); + + qts = ax650x_pyramid_start_with_emmc(); + g_assert_cmphex(qtest_readw(qts, + AX650X_EMMC_BASE + SDHC_NORINTSTS) & + SDHC_NIS_CMDCMP, ==, SDHC_NIS_CMDCMP); + pending_addr = AX650X_GIC_DIST_BASE + AX650X_GICD_ISPENDR + + (AX650X_EMMC_INTID / 32) * sizeof(uint32_t); + pending_mask = 1U << (AX650X_EMMC_INTID % 32); + g_assert_cmphex(qtest_readl(qts, pending_addr) & pending_mask, ==, + pending_mask); + + ret = sdhci_read_cmd(qts, AX650X_EMMC_BASE, (char *)readback, + sizeof(readback)); + g_assert_cmpint(ret, ==, sizeof(readback)); + g_assert_cmpmem(readback, sizeof(readback), source, sizeof(source)); + + sdhci_write_cmd(qts, AX650X_EMMC_BASE, (char *)written, + sizeof(written), AX650X_EMMC_BLOCK_SIZE); + qtest_quit(qts); + + fd = open(emmc_path, O_RDONLY); + g_assert_cmpint(fd, >=, 0); + ret = pread(fd, readback, sizeof(readback), 0); + g_assert_cmpint(ret, ==, sizeof(readback)); + close(fd); + g_assert_cmpmem(readback, sizeof(readback), written, sizeof(written)); +} + +static void test_emmc_auto_cmd23(void) +{ + uint8_t expected[AX650X_EMMC_BLOCK_SIZE]; + uint8_t readback[AX650X_EMMC_BLOCK_SIZE]; + QTestState *qts = ax650x_pyramid_start_with_emmc(); + uint32_t status; + int fd; + ssize_t ret; + + for (unsigned int i = 0; i < sizeof(expected); i++) { + expected[i] = (i * 5) % 251 + 1; + } + + qtest_writel(qts, AX650X_EMMC_BASE + SDHC_ARGUMENT2, 1); + sdhci_cmd_regs(qts, AX650X_EMMC_BASE, sizeof(expected), 1, + AX650X_EMMC_BLOCK_SIZE, + SDHC_TRNS_MULTI | SDHC_TRNS_AUTO_CMD23 | + SDHC_TRNS_BLK_CNT_EN, + SDHC_WRITE_MULTIPLE_BLOCK | SDHC_CMD_DATA_PRESENT); + for (unsigned int i = 0; i < sizeof(expected); i += sizeof(uint32_t)) { + qtest_writel(qts, AX650X_EMMC_BASE + SDHC_BDATA, + ldl_le_p(&expected[i])); + } + + sdhci_cmd_regs(qts, AX650X_EMMC_BASE, 0, 0, 1 << 16, 0, + SDHC_EMMC_SEND_STATUS | SDHC_CMD_RESPONSE); + status = qtest_readl(qts, AX650X_EMMC_BASE + SDHC_RSPREG0); + g_assert_cmphex(status & SDHC_R1_READY_FOR_DATA, ==, + SDHC_R1_READY_FOR_DATA); + g_assert_cmphex(status & SDHC_R1_CURRENT_STATE_MASK, ==, + SDHC_R1_STATE_TRAN); + qtest_quit(qts); + + fd = open(emmc_path, O_RDONLY); + g_assert_cmpint(fd, >=, 0); + ret = pread(fd, readback, sizeof(readback), AX650X_EMMC_BLOCK_SIZE); + g_assert_cmpint(ret, ==, sizeof(readback)); + close(fd); + g_assert_cmpmem(readback, sizeof(readback), expected, sizeof(expected)); +} + +static void emmc_drive_create(void) +{ + GError *error = NULL; + int fd; + + fd = g_file_open_tmp("ax650x-emmc-XXXXXX", &emmc_path, &error); + if (fd < 0) { + g_error("unable to create eMMC image: %s", error->message); + } + g_assert_nonnull(emmc_path); + g_assert_cmpint(ftruncate(fd, AX650X_EMMC_TEST_IMAGE_SIZE), ==, 0); + close(fd); +} + +static void emmc_drive_destroy(void) +{ + unlink(emmc_path); + g_free(emmc_path); +} + +int main(int argc, char **argv) +{ + int ret; + + emmc_drive_create(); + g_test_init(&argc, &argv, NULL); + + qtest_add_func("ax650x-pyramid/cpu-topology", test_cpu_topology); + qtest_add_func("ax650x-pyramid/memory-and-gic", test_memory_and_gic); + qtest_add_func("ax650x-pyramid/uart-irq-and-reset", + test_uart_irq_and_reset); + qtest_add_func("ax650x-pyramid/uart-extension-registers", + test_uart_extension_registers); + qtest_add_func("ax650x-pyramid/dwmac-registers-glue-and-reset", + test_dwmac_registers_glue_and_reset); + qtest_add_func("ax650x-pyramid/dwmac-40bit-dma-and-irq", + test_dwmac_40bit_dma_and_irq); + qtest_add_func("ax650x-pyramid/emmc-registers-and-reset", + test_emmc_registers_and_reset); + qtest_add_func("ax650x-pyramid/emmc-block-io-and-irq", + test_emmc_block_io_and_irq); + qtest_add_func("ax650x-pyramid/emmc-auto-cmd23", + test_emmc_auto_cmd23); + + ret = g_test_run(); + emmc_drive_destroy(); + return ret; +} diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 48359142c3cff..8a45bc9ad9dc5 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -258,6 +258,7 @@ qtests_arm = \ # TODO: once aarch64 TCG is fixed on ARM 32 bit host, make bios-tables-test unconditional qtests_aarch64 = \ (cpu != 'arm' and unpack_edk2_blobs ? ['bios-tables-test'] : []) + \ + (config_all_devices.has_key('CONFIG_AX650X_PYRAMID') ? ['ax650x-pyramid-test'] : []) + \ (config_all_accel.has_key('CONFIG_TCG') and config_all_devices.has_key('CONFIG_TPM_TIS_SYSBUS') ? \ ['tpm-tis-device-test', 'tpm-tis-device-swtpm-test'] : []) + \ (config_all_devices.has_key('CONFIG_XLNX_ZYNQMP_ARM') ? ['xlnx-can-test', 'fuzz-xlnx-dp-test'] : []) + \