diff options
author | Richard Henderson <richard.henderson@linaro.org> | 2023-06-06 12:11:34 -0700 |
---|---|---|
committer | Richard Henderson <richard.henderson@linaro.org> | 2023-06-06 12:11:34 -0700 |
commit | f5e6786de4815751b0a3d2235c760361f228ea48 (patch) | |
tree | 544d0ff2d361be94e83bc620ea0e57b2886487ac /hw | |
parent | c0dde5fc5ccce56b69095bc29af72987efd65d1e (diff) | |
parent | f9ac778898cb28307e0f91421aba34d43c34b679 (diff) | |
download | qemu-f5e6786de4815751b0a3d2235c760361f228ea48.zip qemu-f5e6786de4815751b0a3d2235c760361f228ea48.tar.gz qemu-f5e6786de4815751b0a3d2235c760361f228ea48.tar.bz2 |
Merge tag 'pull-target-arm-20230606' of https://git.linaro.org/people/pmaydell/qemu-arm into staging
target-arm queue:
* Support gdbstub (guest debug) in HVF
* xnlx-versal: Support CANFD controller
* bpim2u: New board model: Banana Pi BPI-M2 Ultra
* Emulate FEAT_LSE2
* allow DC CVA[D]P in user mode emulation
* trap DCC access in user mode emulation
# -----BEGIN PGP SIGNATURE-----
#
# iQJNBAABCAA3FiEE4aXFk81BneKOgxXPPCUl7RQ2DN4FAmR/AKUZHHBldGVyLm1h
# eWRlbGxAbGluYXJvLm9yZwAKCRA8JSXtFDYM3jzIEACNepQGY44yPhrEG+wD4WAB
# fH670KI33HcsFd2rGsC369gcssQbRIW/29reOzNhRMuol+kHI6OFaONpuKSdO0Rz
# TLVIsnT2Uq8KwbYfLtDQt5knj027amPy75d4re8wIK1eZB4dOIHysqAvQrJYeync
# 9obKku8xXGLwZh/mYHoVgHcZU0cPJO9nri39n1tV3JUBsgmqEURjzbZrMcF+yMX7
# bUzOYQvC1Iedmo+aWfx43u82AlNQFz1lsqmnQj7Z5rvv0HT+BRF5WzVMP0qRh5+Z
# njkqmBH9xb9kkgeHmeMvHpWox+J+obeSmVg/4gDNlJpThmpuU0Vr7EXUN3MBQlV9
# lhyy6zrTwC/BToiQqdT2dnpao9FzXy5exfnqi/py5IuqfjAzSO+p61LlPPZ4cJri
# pCK4yq2gzQXYfrlZkUJipvRMH8Xa4IdQx+w7lXrQoJdduF4/+6aJW/GAWSu0e7eC
# zgBwaJjI7ENce8ixJnuEFUxUnaBo8dl72a0PGA1UU8PL+cJNOIpyhPk4goWQprdn
# iFF4ZnjhBRZ2gk/4HGD9u5Vo2lNqP93YS5QhkGkF+HJsBmcOZgidIUpfHhPQvvHO
# Np196T2cAETCWGV1xG4CaTpxN2ndRReq3C0/mzfhIbwhXEACtvAiSlO4KB8t6pJj
# MzinCABXHcovJbGbxZ9j6w==
# =8SdN
# -----END PGP SIGNATURE-----
# gpg: Signature made Tue 06 Jun 2023 02:47:17 AM PDT
# gpg: using RSA key E1A5C593CD419DE28E8315CF3C2525ED14360CDE
# gpg: issuer "peter.maydell@linaro.org"
# gpg: Good signature from "Peter Maydell <peter.maydell@linaro.org>" [full]
# gpg: aka "Peter Maydell <pmaydell@gmail.com>" [full]
# gpg: aka "Peter Maydell <pmaydell@chiark.greenend.org.uk>" [full]
# gpg: aka "Peter Maydell <peter@archaic.org.uk>" [unknown]
* tag 'pull-target-arm-20230606' of https://git.linaro.org/people/pmaydell/qemu-arm: (42 commits)
target/arm: trap DCC access in user mode emulation
tests/tcg/aarch64: add DC CVA[D]P tests
target/arm: allow DC CVA[D]P in user mode emulation
target/arm: Enable FEAT_LSE2 for -cpu max
tests/tcg/multiarch: Adjust sigbus.c
tests/tcg/aarch64: Use stz2g in mte-7.c
target/arm: Move mte check for store-exclusive
target/arm: Relax ordered/atomic alignment checks for LSE2
target/arm: Add SCTLR.nAA to TBFLAG_A64
target/arm: Check alignment in helper_mte_check
target/arm: Pass single_memop to gen_mte_checkN
target/arm: Pass memop to gen_mte_check1*
target/arm: Hoist finalize_memop out of do_fp_{ld, st}
target/arm: Hoist finalize_memop out of do_gpr_{ld, st}
target/arm: Load/store integer pair with one tcg operation
target/arm: Sink gen_mte_check1 into load/store_exclusive
target/arm: Use tcg_gen_qemu_{ld, st}_i128 in gen_sve_{ld, st}r
target/arm: Use tcg_gen_qemu_st_i128 for STZG, STZ2G
target/arm: Use tcg_gen_qemu_{st, ld}_i128 for do_fp_{st, ld}
target/arm: Use tcg_gen_qemu_ld_i128 for LDXP
...
Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
Diffstat (limited to 'hw')
-rw-r--r-- | hw/arm/Kconfig | 14 | ||||
-rw-r--r-- | hw/arm/allwinner-r40.c | 526 | ||||
-rw-r--r-- | hw/arm/bananapi_m2u.c | 145 | ||||
-rw-r--r-- | hw/arm/meson.build | 1 | ||||
-rw-r--r-- | hw/arm/xlnx-versal-virt.c | 53 | ||||
-rw-r--r-- | hw/arm/xlnx-versal.c | 37 | ||||
-rw-r--r-- | hw/misc/Kconfig | 5 | ||||
-rw-r--r-- | hw/misc/allwinner-r40-ccu.c | 209 | ||||
-rw-r--r-- | hw/misc/allwinner-r40-dramc.c | 513 | ||||
-rw-r--r-- | hw/misc/allwinner-sramc.c | 184 | ||||
-rw-r--r-- | hw/misc/axp209.c | 238 | ||||
-rw-r--r-- | hw/misc/axp2xx.c | 283 | ||||
-rw-r--r-- | hw/misc/meson.build | 5 | ||||
-rw-r--r-- | hw/misc/trace-events | 26 | ||||
-rw-r--r-- | hw/net/can/meson.build | 1 | ||||
-rw-r--r-- | hw/net/can/trace-events | 7 | ||||
-rw-r--r-- | hw/net/can/xlnx-versal-canfd.c | 2107 | ||||
-rw-r--r-- | hw/sd/allwinner-sdhost.c | 72 |
18 files changed, 4178 insertions, 248 deletions
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index acc4371..2159de3 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -383,7 +383,7 @@ config ALLWINNER_A10 select ALLWINNER_WDT select ALLWINNER_EMAC select ALLWINNER_I2C - select AXP209_PMU + select AXP2XX_PMU select SERIAL select UNIMP @@ -403,6 +403,18 @@ config ALLWINNER_H3 select USB_EHCI_SYSBUS select SD +config ALLWINNER_R40 + bool + default y if TCG && ARM + select ALLWINNER_SRAMC + select ALLWINNER_A10_PIT + select AXP2XX_PMU + select SERIAL + select ARM_TIMER + select ARM_GIC + select UNIMP + select SD + config RASPI bool default y diff --git a/hw/arm/allwinner-r40.c b/hw/arm/allwinner-r40.c new file mode 100644 index 0000000..7d29eb2 --- /dev/null +++ b/hw/arm/allwinner-r40.c @@ -0,0 +1,526 @@ +/* + * Allwinner R40/A40i/T3 System on Chip emulation + * + * Copyright (C) 2023 qianfan Zhao <qianfanguijin@163.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "qemu/bswap.h" +#include "qemu/module.h" +#include "qemu/units.h" +#include "hw/qdev-core.h" +#include "hw/sysbus.h" +#include "hw/char/serial.h" +#include "hw/misc/unimp.h" +#include "hw/usb/hcd-ehci.h" +#include "hw/loader.h" +#include "sysemu/sysemu.h" +#include "hw/arm/allwinner-r40.h" +#include "hw/misc/allwinner-r40-dramc.h" + +/* Memory map */ +const hwaddr allwinner_r40_memmap[] = { + [AW_R40_DEV_SRAM_A1] = 0x00000000, + [AW_R40_DEV_SRAM_A2] = 0x00004000, + [AW_R40_DEV_SRAM_A3] = 0x00008000, + [AW_R40_DEV_SRAM_A4] = 0x0000b400, + [AW_R40_DEV_SRAMC] = 0x01c00000, + [AW_R40_DEV_EMAC] = 0x01c0b000, + [AW_R40_DEV_MMC0] = 0x01c0f000, + [AW_R40_DEV_MMC1] = 0x01c10000, + [AW_R40_DEV_MMC2] = 0x01c11000, + [AW_R40_DEV_MMC3] = 0x01c12000, + [AW_R40_DEV_CCU] = 0x01c20000, + [AW_R40_DEV_PIT] = 0x01c20c00, + [AW_R40_DEV_UART0] = 0x01c28000, + [AW_R40_DEV_UART1] = 0x01c28400, + [AW_R40_DEV_UART2] = 0x01c28800, + [AW_R40_DEV_UART3] = 0x01c28c00, + [AW_R40_DEV_UART4] = 0x01c29000, + [AW_R40_DEV_UART5] = 0x01c29400, + [AW_R40_DEV_UART6] = 0x01c29800, + [AW_R40_DEV_UART7] = 0x01c29c00, + [AW_R40_DEV_TWI0] = 0x01c2ac00, + [AW_R40_DEV_GMAC] = 0x01c50000, + [AW_R40_DEV_DRAMCOM] = 0x01c62000, + [AW_R40_DEV_DRAMCTL] = 0x01c63000, + [AW_R40_DEV_DRAMPHY] = 0x01c65000, + [AW_R40_DEV_GIC_DIST] = 0x01c81000, + [AW_R40_DEV_GIC_CPU] = 0x01c82000, + [AW_R40_DEV_GIC_HYP] = 0x01c84000, + [AW_R40_DEV_GIC_VCPU] = 0x01c86000, + [AW_R40_DEV_SDRAM] = 0x40000000 +}; + +/* List of unimplemented devices */ +struct AwR40Unimplemented { + const char *device_name; + hwaddr base; + hwaddr size; +}; + +static struct AwR40Unimplemented r40_unimplemented[] = { + { "d-engine", 0x01000000, 4 * MiB }, + { "d-inter", 0x01400000, 128 * KiB }, + { "dma", 0x01c02000, 4 * KiB }, + { "nfdc", 0x01c03000, 4 * KiB }, + { "ts", 0x01c04000, 4 * KiB }, + { "spi0", 0x01c05000, 4 * KiB }, + { "spi1", 0x01c06000, 4 * KiB }, + { "cs0", 0x01c09000, 4 * KiB }, + { "keymem", 0x01c0a000, 4 * KiB }, + { "usb0-otg", 0x01c13000, 4 * KiB }, + { "usb0-host", 0x01c14000, 4 * KiB }, + { "crypto", 0x01c15000, 4 * KiB }, + { "spi2", 0x01c17000, 4 * KiB }, + { "sata", 0x01c18000, 4 * KiB }, + { "usb1-host", 0x01c19000, 4 * KiB }, + { "sid", 0x01c1b000, 4 * KiB }, + { "usb2-host", 0x01c1c000, 4 * KiB }, + { "cs1", 0x01c1d000, 4 * KiB }, + { "spi3", 0x01c1f000, 4 * KiB }, + { "rtc", 0x01c20400, 1 * KiB }, + { "pio", 0x01c20800, 1 * KiB }, + { "owa", 0x01c21000, 1 * KiB }, + { "ac97", 0x01c21400, 1 * KiB }, + { "cir0", 0x01c21800, 1 * KiB }, + { "cir1", 0x01c21c00, 1 * KiB }, + { "pcm0", 0x01c22000, 1 * KiB }, + { "pcm1", 0x01c22400, 1 * KiB }, + { "pcm2", 0x01c22800, 1 * KiB }, + { "audio", 0x01c22c00, 1 * KiB }, + { "keypad", 0x01c23000, 1 * KiB }, + { "pwm", 0x01c23400, 1 * KiB }, + { "keyadc", 0x01c24400, 1 * KiB }, + { "ths", 0x01c24c00, 1 * KiB }, + { "rtp", 0x01c25000, 1 * KiB }, + { "pmu", 0x01c25400, 1 * KiB }, + { "cpu-cfg", 0x01c25c00, 1 * KiB }, + { "uart0", 0x01c28000, 1 * KiB }, + { "uart1", 0x01c28400, 1 * KiB }, + { "uart2", 0x01c28800, 1 * KiB }, + { "uart3", 0x01c28c00, 1 * KiB }, + { "uart4", 0x01c29000, 1 * KiB }, + { "uart5", 0x01c29400, 1 * KiB }, + { "uart6", 0x01c29800, 1 * KiB }, + { "uart7", 0x01c29c00, 1 * KiB }, + { "ps20", 0x01c2a000, 1 * KiB }, + { "ps21", 0x01c2a400, 1 * KiB }, + { "twi1", 0x01c2b000, 1 * KiB }, + { "twi2", 0x01c2b400, 1 * KiB }, + { "twi3", 0x01c2b800, 1 * KiB }, + { "twi4", 0x01c2c000, 1 * KiB }, + { "scr", 0x01c2c400, 1 * KiB }, + { "tvd-top", 0x01c30000, 4 * KiB }, + { "tvd0", 0x01c31000, 4 * KiB }, + { "tvd1", 0x01c32000, 4 * KiB }, + { "tvd2", 0x01c33000, 4 * KiB }, + { "tvd3", 0x01c34000, 4 * KiB }, + { "gpu", 0x01c40000, 64 * KiB }, + { "hstmr", 0x01c60000, 4 * KiB }, + { "tcon-top", 0x01c70000, 4 * KiB }, + { "lcd0", 0x01c71000, 4 * KiB }, + { "lcd1", 0x01c72000, 4 * KiB }, + { "tv0", 0x01c73000, 4 * KiB }, + { "tv1", 0x01c74000, 4 * KiB }, + { "tve-top", 0x01c90000, 16 * KiB }, + { "tve0", 0x01c94000, 16 * KiB }, + { "tve1", 0x01c98000, 16 * KiB }, + { "mipi_dsi", 0x01ca0000, 4 * KiB }, + { "mipi_dphy", 0x01ca1000, 4 * KiB }, + { "ve", 0x01d00000, 1024 * KiB }, + { "mp", 0x01e80000, 128 * KiB }, + { "hdmi", 0x01ee0000, 128 * KiB }, + { "prcm", 0x01f01400, 1 * KiB }, + { "debug", 0x3f500000, 64 * KiB }, + { "cpubist", 0x3f501000, 4 * KiB }, + { "dcu", 0x3fff0000, 64 * KiB }, + { "hstmr", 0x01c60000, 4 * KiB }, + { "brom", 0xffff0000, 36 * KiB } +}; + +/* Per Processor Interrupts */ +enum { + AW_R40_GIC_PPI_MAINT = 9, + AW_R40_GIC_PPI_HYPTIMER = 10, + AW_R40_GIC_PPI_VIRTTIMER = 11, + AW_R40_GIC_PPI_SECTIMER = 13, + AW_R40_GIC_PPI_PHYSTIMER = 14 +}; + +/* Shared Processor Interrupts */ +enum { + AW_R40_GIC_SPI_UART0 = 1, + AW_R40_GIC_SPI_UART1 = 2, + AW_R40_GIC_SPI_UART2 = 3, + AW_R40_GIC_SPI_UART3 = 4, + AW_R40_GIC_SPI_TWI0 = 7, + AW_R40_GIC_SPI_UART4 = 17, + AW_R40_GIC_SPI_UART5 = 18, + AW_R40_GIC_SPI_UART6 = 19, + AW_R40_GIC_SPI_UART7 = 20, + AW_R40_GIC_SPI_TIMER0 = 22, + AW_R40_GIC_SPI_TIMER1 = 23, + AW_R40_GIC_SPI_MMC0 = 32, + AW_R40_GIC_SPI_MMC1 = 33, + AW_R40_GIC_SPI_MMC2 = 34, + AW_R40_GIC_SPI_MMC3 = 35, + AW_R40_GIC_SPI_EMAC = 55, + AW_R40_GIC_SPI_GMAC = 85, +}; + +/* Allwinner R40 general constants */ +enum { + AW_R40_GIC_NUM_SPI = 128 +}; + +#define BOOT0_MAGIC "eGON.BT0" + +/* The low 8-bits of the 'boot_media' field in the SPL header */ +#define SUNXI_BOOTED_FROM_MMC0 0 +#define SUNXI_BOOTED_FROM_NAND 1 +#define SUNXI_BOOTED_FROM_MMC2 2 +#define SUNXI_BOOTED_FROM_SPI 3 + +struct boot_file_head { + uint32_t b_instruction; + uint8_t magic[8]; + uint32_t check_sum; + uint32_t length; + uint32_t pub_head_size; + uint32_t fel_script_address; + uint32_t fel_uEnv_length; + uint32_t dt_name_offset; + uint32_t dram_size; + uint32_t boot_media; + uint32_t string_pool[13]; +}; + +bool allwinner_r40_bootrom_setup(AwR40State *s, BlockBackend *blk, int unit) +{ + const int64_t rom_size = 32 * KiB; + g_autofree uint8_t *buffer = g_new0(uint8_t, rom_size); + struct boot_file_head *head = (struct boot_file_head *)buffer; + + if (blk_pread(blk, 8 * KiB, rom_size, buffer, 0) < 0) { + error_setg(&error_fatal, "%s: failed to read BlockBackend data", + __func__); + return false; + } + + /* we only check the magic string here. */ + if (memcmp(head->magic, BOOT0_MAGIC, sizeof(head->magic))) { + return false; + } + + /* + * Simulate the behavior of the bootROM, it will change the boot_media + * flag to indicate where the chip is booting from. R40 can boot from + * mmc0 or mmc2, the default value of boot_media is zero + * (SUNXI_BOOTED_FROM_MMC0), let's fix this flag when it is booting from + * the others. + */ + if (unit == 2) { + head->boot_media = cpu_to_le32(SUNXI_BOOTED_FROM_MMC2); + } else { + head->boot_media = cpu_to_le32(SUNXI_BOOTED_FROM_MMC0); + } + + rom_add_blob("allwinner-r40.bootrom", buffer, rom_size, + rom_size, s->memmap[AW_R40_DEV_SRAM_A1], + NULL, NULL, NULL, NULL, false); + return true; +} + +static void allwinner_r40_init(Object *obj) +{ + static const char *mmc_names[AW_R40_NUM_MMCS] = { + "mmc0", "mmc1", "mmc2", "mmc3" + }; + AwR40State *s = AW_R40(obj); + + s->memmap = allwinner_r40_memmap; + + for (int i = 0; i < AW_R40_NUM_CPUS; i++) { + object_initialize_child(obj, "cpu[*]", &s->cpus[i], + ARM_CPU_TYPE_NAME("cortex-a7")); + } + + object_initialize_child(obj, "gic", &s->gic, TYPE_ARM_GIC); + + object_initialize_child(obj, "timer", &s->timer, TYPE_AW_A10_PIT); + object_property_add_alias(obj, "clk0-freq", OBJECT(&s->timer), + "clk0-freq"); + object_property_add_alias(obj, "clk1-freq", OBJECT(&s->timer), + "clk1-freq"); + + object_initialize_child(obj, "ccu", &s->ccu, TYPE_AW_R40_CCU); + + for (int i = 0; i < AW_R40_NUM_MMCS; i++) { + object_initialize_child(obj, mmc_names[i], &s->mmc[i], + TYPE_AW_SDHOST_SUN50I_A64); + } + + object_initialize_child(obj, "twi0", &s->i2c0, TYPE_AW_I2C_SUN6I); + + object_initialize_child(obj, "emac", &s->emac, TYPE_AW_EMAC); + object_initialize_child(obj, "gmac", &s->gmac, TYPE_AW_SUN8I_EMAC); + object_property_add_alias(obj, "gmac-phy-addr", + OBJECT(&s->gmac), "phy-addr"); + + object_initialize_child(obj, "dramc", &s->dramc, TYPE_AW_R40_DRAMC); + object_property_add_alias(obj, "ram-addr", OBJECT(&s->dramc), + "ram-addr"); + object_property_add_alias(obj, "ram-size", OBJECT(&s->dramc), + "ram-size"); + + object_initialize_child(obj, "sramc", &s->sramc, TYPE_AW_SRAMC_SUN8I_R40); +} + +static void allwinner_r40_realize(DeviceState *dev, Error **errp) +{ + const char *r40_nic_models[] = { "gmac", "emac", NULL }; + AwR40State *s = AW_R40(dev); + unsigned i; + + /* CPUs */ + for (i = 0; i < AW_R40_NUM_CPUS; i++) { + + /* + * Disable secondary CPUs. Guest EL3 firmware will start + * them via CPU reset control registers. + */ + qdev_prop_set_bit(DEVICE(&s->cpus[i]), "start-powered-off", + i > 0); + + /* All exception levels required */ + qdev_prop_set_bit(DEVICE(&s->cpus[i]), "has_el3", true); + qdev_prop_set_bit(DEVICE(&s->cpus[i]), "has_el2", true); + + /* Mark realized */ + qdev_realize(DEVICE(&s->cpus[i]), NULL, &error_fatal); + } + + /* Generic Interrupt Controller */ + qdev_prop_set_uint32(DEVICE(&s->gic), "num-irq", AW_R40_GIC_NUM_SPI + + GIC_INTERNAL); + qdev_prop_set_uint32(DEVICE(&s->gic), "revision", 2); + qdev_prop_set_uint32(DEVICE(&s->gic), "num-cpu", AW_R40_NUM_CPUS); + qdev_prop_set_bit(DEVICE(&s->gic), "has-security-extensions", false); + qdev_prop_set_bit(DEVICE(&s->gic), "has-virtualization-extensions", true); + sysbus_realize(SYS_BUS_DEVICE(&s->gic), &error_fatal); + + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gic), 0, s->memmap[AW_R40_DEV_GIC_DIST]); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gic), 1, s->memmap[AW_R40_DEV_GIC_CPU]); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gic), 2, s->memmap[AW_R40_DEV_GIC_HYP]); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gic), 3, s->memmap[AW_R40_DEV_GIC_VCPU]); + + /* + * Wire the outputs from each CPU's generic timer and the GICv2 + * maintenance interrupt signal to the appropriate GIC PPI inputs, + * and the GIC's IRQ/FIQ/VIRQ/VFIQ interrupt outputs to the CPU's inputs. + */ + for (i = 0; i < AW_R40_NUM_CPUS; i++) { + DeviceState *cpudev = DEVICE(&s->cpus[i]); + int ppibase = AW_R40_GIC_NUM_SPI + i * GIC_INTERNAL + GIC_NR_SGIS; + int irq; + /* + * Mapping from the output timer irq lines from the CPU to the + * GIC PPI inputs used for this board. + */ + const int timer_irq[] = { + [GTIMER_PHYS] = AW_R40_GIC_PPI_PHYSTIMER, + [GTIMER_VIRT] = AW_R40_GIC_PPI_VIRTTIMER, + [GTIMER_HYP] = AW_R40_GIC_PPI_HYPTIMER, + [GTIMER_SEC] = AW_R40_GIC_PPI_SECTIMER, + }; + + /* Connect CPU timer outputs to GIC PPI inputs */ + for (irq = 0; irq < ARRAY_SIZE(timer_irq); irq++) { + qdev_connect_gpio_out(cpudev, irq, + qdev_get_gpio_in(DEVICE(&s->gic), + ppibase + timer_irq[irq])); + } + + /* Connect GIC outputs to CPU interrupt inputs */ + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gic), i, + qdev_get_gpio_in(cpudev, ARM_CPU_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gic), i + AW_R40_NUM_CPUS, + qdev_get_gpio_in(cpudev, ARM_CPU_FIQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gic), i + (2 * AW_R40_NUM_CPUS), + qdev_get_gpio_in(cpudev, ARM_CPU_VIRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gic), i + (3 * AW_R40_NUM_CPUS), + qdev_get_gpio_in(cpudev, ARM_CPU_VFIQ)); + + /* GIC maintenance signal */ + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gic), i + (4 * AW_R40_NUM_CPUS), + qdev_get_gpio_in(DEVICE(&s->gic), + ppibase + AW_R40_GIC_PPI_MAINT)); + } + + /* Timer */ + sysbus_realize(SYS_BUS_DEVICE(&s->timer), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->timer), 0, s->memmap[AW_R40_DEV_PIT]); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->timer), 0, + qdev_get_gpio_in(DEVICE(&s->gic), + AW_R40_GIC_SPI_TIMER0)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->timer), 1, + qdev_get_gpio_in(DEVICE(&s->gic), + AW_R40_GIC_SPI_TIMER1)); + + /* SRAM */ + sysbus_realize(SYS_BUS_DEVICE(&s->sramc), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->sramc), 0, s->memmap[AW_R40_DEV_SRAMC]); + + memory_region_init_ram(&s->sram_a1, OBJECT(dev), "sram A1", + 16 * KiB, &error_abort); + memory_region_init_ram(&s->sram_a2, OBJECT(dev), "sram A2", + 16 * KiB, &error_abort); + memory_region_init_ram(&s->sram_a3, OBJECT(dev), "sram A3", + 13 * KiB, &error_abort); + memory_region_init_ram(&s->sram_a4, OBJECT(dev), "sram A4", + 3 * KiB, &error_abort); + memory_region_add_subregion(get_system_memory(), + s->memmap[AW_R40_DEV_SRAM_A1], &s->sram_a1); + memory_region_add_subregion(get_system_memory(), + s->memmap[AW_R40_DEV_SRAM_A2], &s->sram_a2); + memory_region_add_subregion(get_system_memory(), + s->memmap[AW_R40_DEV_SRAM_A3], &s->sram_a3); + memory_region_add_subregion(get_system_memory(), + s->memmap[AW_R40_DEV_SRAM_A4], &s->sram_a4); + + /* Clock Control Unit */ + sysbus_realize(SYS_BUS_DEVICE(&s->ccu), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->ccu), 0, s->memmap[AW_R40_DEV_CCU]); + + /* SD/MMC */ + for (int i = 0; i < AW_R40_NUM_MMCS; i++) { + qemu_irq irq = qdev_get_gpio_in(DEVICE(&s->gic), + AW_R40_GIC_SPI_MMC0 + i); + const hwaddr addr = s->memmap[AW_R40_DEV_MMC0 + i]; + + object_property_set_link(OBJECT(&s->mmc[i]), "dma-memory", + OBJECT(get_system_memory()), &error_fatal); + sysbus_realize(SYS_BUS_DEVICE(&s->mmc[i]), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->mmc[i]), 0, addr); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->mmc[i]), 0, irq); + } + + /* UART0. For future clocktree API: All UARTS are connected to APB2_CLK. */ + for (int i = 0; i < AW_R40_NUM_UARTS; i++) { + static const int uart_irqs[AW_R40_NUM_UARTS] = { + AW_R40_GIC_SPI_UART0, + AW_R40_GIC_SPI_UART1, + AW_R40_GIC_SPI_UART2, + AW_R40_GIC_SPI_UART3, + AW_R40_GIC_SPI_UART4, + AW_R40_GIC_SPI_UART5, + AW_R40_GIC_SPI_UART6, + AW_R40_GIC_SPI_UART7, + }; + const hwaddr addr = s->memmap[AW_R40_DEV_UART0 + i]; + + serial_mm_init(get_system_memory(), addr, 2, + qdev_get_gpio_in(DEVICE(&s->gic), uart_irqs[i]), + 115200, serial_hd(i), DEVICE_NATIVE_ENDIAN); + } + + /* I2C */ + sysbus_realize(SYS_BUS_DEVICE(&s->i2c0), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->i2c0), 0, s->memmap[AW_R40_DEV_TWI0]); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2c0), 0, + qdev_get_gpio_in(DEVICE(&s->gic), AW_R40_GIC_SPI_TWI0)); + + /* DRAMC */ + sysbus_realize(SYS_BUS_DEVICE(&s->dramc), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->dramc), 0, + s->memmap[AW_R40_DEV_DRAMCOM]); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->dramc), 1, + s->memmap[AW_R40_DEV_DRAMCTL]); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->dramc), 2, + s->memmap[AW_R40_DEV_DRAMPHY]); + + /* nic support gmac and emac */ + for (int i = 0; i < ARRAY_SIZE(r40_nic_models) - 1; i++) { + NICInfo *nic = &nd_table[i]; + + if (!nic->used) { + continue; + } + if (qemu_show_nic_models(nic->model, r40_nic_models)) { + exit(0); + } + + switch (qemu_find_nic_model(nic, r40_nic_models, r40_nic_models[0])) { + case 0: /* gmac */ + qdev_set_nic_properties(DEVICE(&s->gmac), nic); + break; + case 1: /* emac */ + qdev_set_nic_properties(DEVICE(&s->emac), nic); + break; + default: + exit(1); + break; + } + } + + /* GMAC */ + object_property_set_link(OBJECT(&s->gmac), "dma-memory", + OBJECT(get_system_memory()), &error_fatal); + sysbus_realize(SYS_BUS_DEVICE(&s->gmac), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gmac), 0, s->memmap[AW_R40_DEV_GMAC]); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gmac), 0, + qdev_get_gpio_in(DEVICE(&s->gic), AW_R40_GIC_SPI_GMAC)); + + /* EMAC */ + sysbus_realize(SYS_BUS_DEVICE(&s->emac), &error_fatal); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->emac), 0, s->memmap[AW_R40_DEV_EMAC]); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->emac), 0, + qdev_get_gpio_in(DEVICE(&s->gic), AW_R40_GIC_SPI_EMAC)); + + /* Unimplemented devices */ + for (i = 0; i < ARRAY_SIZE(r40_unimplemented); i++) { + create_unimplemented_device(r40_unimplemented[i].device_name, + r40_unimplemented[i].base, + r40_unimplemented[i].size); + } +} + +static void allwinner_r40_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = allwinner_r40_realize; + /* Reason: uses serial_hd() in realize function */ + dc->user_creatable = false; +} + +static const TypeInfo allwinner_r40_type_info = { + .name = TYPE_AW_R40, + .parent = TYPE_DEVICE, + .instance_size = sizeof(AwR40State), + .instance_init = allwinner_r40_init, + .class_init = allwinner_r40_class_init, +}; + +static void allwinner_r40_register_types(void) +{ + type_register_static(&allwinner_r40_type_info); +} + +type_init(allwinner_r40_register_types) diff --git a/hw/arm/bananapi_m2u.c b/hw/arm/bananapi_m2u.c new file mode 100644 index 0000000..74121d8 --- /dev/null +++ b/hw/arm/bananapi_m2u.c @@ -0,0 +1,145 @@ +/* + * Bananapi M2U emulation + * + * Copyright (C) 2023 qianfan Zhao <qianfanguijin@163.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "exec/address-spaces.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "hw/boards.h" +#include "hw/i2c/i2c.h" +#include "hw/qdev-properties.h" +#include "hw/arm/allwinner-r40.h" + +static struct arm_boot_info bpim2u_binfo; + +/* + * R40 can boot from mmc0 and mmc2, and bpim2u has two mmc interface, one is + * connected to sdcard and another mount an emmc media. + * Attach the mmc driver and try loading bootloader. + */ +static void mmc_attach_drive(AwR40State *s, AwSdHostState *mmc, int unit, + bool load_bootroom, bool *bootroom_loaded) +{ + DriveInfo *di = drive_get(IF_SD, 0, unit); + BlockBackend *blk = di ? blk_by_legacy_dinfo(di) : NULL; + BusState *bus; + DeviceState *carddev; + + bus = qdev_get_child_bus(DEVICE(mmc), "sd-bus"); + if (bus == NULL) { + error_report("No SD bus found in SOC object"); + exit(1); + } + + carddev = qdev_new(TYPE_SD_CARD); + qdev_prop_set_drive_err(carddev, "drive", blk, &error_fatal); + qdev_realize_and_unref(carddev, bus, &error_fatal); + + if (load_bootroom && blk && blk_is_available(blk)) { + /* Use Boot ROM to copy data from SD card to SRAM */ + *bootroom_loaded = allwinner_r40_bootrom_setup(s, blk, unit); + } +} + +static void bpim2u_init(MachineState *machine) +{ + bool bootroom_loaded = false; + AwR40State *r40; + I2CBus *i2c; + + /* BIOS is not supported by this board */ + if (machine->firmware) { + error_report("BIOS not supported for this machine"); + exit(1); + } + + /* Only allow Cortex-A7 for this board */ + if (strcmp(machine->cpu_type, ARM_CPU_TYPE_NAME("cortex-a7")) != 0) { + error_report("This board can only be used with cortex-a7 CPU"); + exit(1); + } + + r40 = AW_R40(object_new(TYPE_AW_R40)); + object_property_add_child(OBJECT(machine), "soc", OBJECT(r40)); + object_unref(OBJECT(r40)); + + /* Setup timer properties */ + object_property_set_int(OBJECT(r40), "clk0-freq", 32768, &error_abort); + object_property_set_int(OBJECT(r40), "clk1-freq", 24 * 1000 * 1000, + &error_abort); + + /* DRAMC */ + r40->ram_size = machine->ram_size / MiB; + object_property_set_uint(OBJECT(r40), "ram-addr", + r40->memmap[AW_R40_DEV_SDRAM], &error_abort); + object_property_set_int(OBJECT(r40), "ram-size", + r40->ram_size, &error_abort); + + /* GMAC PHY */ + object_property_set_uint(OBJECT(r40), "gmac-phy-addr", 1, &error_abort); + + /* Mark R40 object realized */ + qdev_realize(DEVICE(r40), NULL, &error_abort); + + /* + * Plug in SD card and try load bootrom, R40 has 4 mmc controllers but can + * only booting from mmc0 and mmc2. + */ + for (int i = 0; i < AW_R40_NUM_MMCS; i++) { + switch (i) { + case 0: + case 2: + mmc_attach_drive(r40, &r40->mmc[i], i, + !machine->kernel_filename && !bootroom_loaded, + &bootroom_loaded); + break; + default: + mmc_attach_drive(r40, &r40->mmc[i], i, false, NULL); + break; + } + } + + /* Connect AXP221 */ + i2c = I2C_BUS(qdev_get_child_bus(DEVICE(&r40->i2c0), "i2c")); + i2c_slave_create_simple(i2c, "axp221_pmu", 0x34); + + /* SDRAM */ + memory_region_add_subregion(get_system_memory(), + r40->memmap[AW_R40_DEV_SDRAM], machine->ram); + + bpim2u_binfo.loader_start = r40->memmap[AW_R40_DEV_SDRAM]; + bpim2u_binfo.ram_size = machine->ram_size; + bpim2u_binfo.psci_conduit = QEMU_PSCI_CONDUIT_SMC; + arm_load_kernel(ARM_CPU(first_cpu), machine, &bpim2u_binfo); +} + +static void bpim2u_machine_init(MachineClass *mc) +{ + mc->desc = "Bananapi M2U (Cortex-A7)"; + mc->init = bpim2u_init; + mc->min_cpus = AW_R40_NUM_CPUS; + mc->max_cpus = AW_R40_NUM_CPUS; + mc->default_cpus = AW_R40_NUM_CPUS; + mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-a7"); + mc->default_ram_size = 1 * GiB; + mc->default_ram_id = "bpim2u.ram"; +} + +DEFINE_MACHINE("bpim2u", bpim2u_machine_init) diff --git a/hw/arm/meson.build b/hw/arm/meson.build index b545ba0..870ec67 100644 --- a/hw/arm/meson.build +++ b/hw/arm/meson.build @@ -37,6 +37,7 @@ arm_ss.add(when: 'CONFIG_OMAP', if_true: files('omap1.c', 'omap2.c')) arm_ss.add(when: 'CONFIG_STRONGARM', if_true: files('strongarm.c')) arm_ss.add(when: 'CONFIG_ALLWINNER_A10', if_true: files('allwinner-a10.c', 'cubieboard.c')) arm_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3.c', 'orangepi.c')) +arm_ss.add(when: 'CONFIG_ALLWINNER_R40', if_true: files('allwinner-r40.c', 'bananapi_m2u.c')) arm_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2836.c', 'raspi.c')) arm_ss.add(when: 'CONFIG_STM32F100_SOC', if_true: files('stm32f100_soc.c')) arm_ss.add(when: 'CONFIG_STM32F205_SOC', if_true: files('stm32f205_soc.c')) diff --git a/hw/arm/xlnx-versal-virt.c b/hw/arm/xlnx-versal-virt.c index 668a9d6..1ee2b86 100644 --- a/hw/arm/xlnx-versal-virt.c +++ b/hw/arm/xlnx-versal-virt.c @@ -40,9 +40,11 @@ struct VersalVirt { uint32_t clk_25Mhz; uint32_t usb; uint32_t dwc; + uint32_t canfd[2]; } phandle; struct arm_boot_info binfo; + CanBusState *canbus[XLNX_VERSAL_NR_CANFD]; struct { bool secure; } cfg; @@ -235,6 +237,38 @@ static void fdt_add_uart_nodes(VersalVirt *s) } } +static void fdt_add_canfd_nodes(VersalVirt *s) +{ + uint64_t addrs[] = { MM_CANFD1, MM_CANFD0 }; + uint32_t size[] = { MM_CANFD1_SIZE, MM_CANFD0_SIZE }; + unsigned int irqs[] = { VERSAL_CANFD1_IRQ_0, VERSAL_CANFD0_IRQ_0 }; + const char clocknames[] = "can_clk\0s_axi_aclk"; + int i; + + /* Create and connect CANFD0 and CANFD1 nodes to canbus0. */ + for (i = 0; i < ARRAY_SIZE(addrs); i++) { + char *name = g_strdup_printf("/canfd@%" PRIx64, addrs[i]); + qemu_fdt_add_subnode(s->fdt, name); + + qemu_fdt_setprop_cell(s->fdt, name, "rx-fifo-depth", 0x40); + qemu_fdt_setprop_cell(s->fdt, name, "tx-mailbox-count", 0x20); + + qemu_fdt_setprop_cells(s->fdt, name, "clocks", + s->phandle.clk_25Mhz, s->phandle.clk_25Mhz); + qemu_fdt_setprop(s->fdt, name, "clock-names", + clocknames, sizeof(clocknames)); + qemu_fdt_setprop_cells(s->fdt, name, "interrupts", + GIC_FDT_IRQ_TYPE_SPI, irqs[i], + GIC_FDT_IRQ_FLAGS_LEVEL_HI); + qemu_fdt_setprop_sized_cells(s->fdt, name, "reg", + 2, addrs[i], 2, size[i]); + qemu_fdt_setprop_string(s->fdt, name, "compatible", + "xlnx,canfd-2.0"); + + g_free(name); + } +} + static void fdt_add_fixed_link_nodes(VersalVirt *s, char *gemname, uint32_t phandle) { @@ -639,12 +673,17 @@ static void versal_virt_init(MachineState *machine) TYPE_XLNX_VERSAL); object_property_set_link(OBJECT(&s->soc), "ddr", OBJECT(machine->ram), &error_abort); + object_property_set_link(OBJECT(&s->soc), "canbus0", OBJECT(s->canbus[0]), + &error_abort); + object_property_set_link(OBJECT(&s->soc), "canbus1", OBJECT(s->canbus[1]), + &error_abort); sysbus_realize(SYS_BUS_DEVICE(&s->soc), &error_fatal); fdt_create(s); create_virtio_regions(s); fdt_add_gem_nodes(s); fdt_add_uart_nodes(s); + fdt_add_canfd_nodes(s); fdt_add_gic_nodes(s); fdt_add_timer_nodes(s); fdt_add_zdma_nodes(s); @@ -712,6 +751,20 @@ static void versal_virt_init(MachineState *machine) static void versal_virt_machine_instance_init(Object *obj) { + VersalVirt *s = XLNX_VERSAL_VIRT_MACHINE(obj); + + /* + * User can set canbus0 and canbus1 properties to can-bus object and connect + * to socketcan(optional) interface via command line. + */ + object_property_add_link(obj, "canbus0", TYPE_CAN_BUS, + (Object **)&s->canbus[0], + object_property_allow_set_link, + 0); + object_property_add_link(obj, "canbus1", TYPE_CAN_BUS, + (Object **)&s->canbus[1], + object_property_allow_set_link, + 0); } static void versal_virt_machine_class_init(ObjectClass *oc, void *data) diff --git a/hw/arm/xlnx-versal.c b/hw/arm/xlnx-versal.c index db1e0de..60bf5fe 100644 --- a/hw/arm/xlnx-versal.c +++ b/hw/arm/xlnx-versal.c @@ -184,6 +184,38 @@ static void versal_create_uarts(Versal *s, qemu_irq *pic) } } +static void versal_create_canfds(Versal *s, qemu_irq *pic) +{ + int i; + uint32_t irqs[] = { VERSAL_CANFD0_IRQ_0, VERSAL_CANFD1_IRQ_0}; + uint64_t addrs[] = { MM_CANFD0, MM_CANFD1 }; + + for (i = 0; i < ARRAY_SIZE(s->lpd.iou.canfd); i++) { + char *name = g_strdup_printf("canfd%d", i); + SysBusDevice *sbd; + MemoryRegion *mr; + + object_initialize_child(OBJECT(s), name, &s->lpd.iou.canfd[i], + TYPE_XILINX_CANFD); + sbd = SYS_BUS_DEVICE(&s->lpd.iou.canfd[i]); + + object_property_set_int(OBJECT(&s->lpd.iou.canfd[i]), "ext_clk_freq", + XLNX_VERSAL_CANFD_REF_CLK , &error_abort); + + object_property_set_link(OBJECT(&s->lpd.iou.canfd[i]), "canfdbus", + OBJECT(s->lpd.iou.canbus[i]), + &error_abort); + + sysbus_realize(sbd, &error_fatal); + + mr = sysbus_mmio_get_region(sbd, 0); + memory_region_add_subregion(&s->mr_ps, addrs[i], mr); + + sysbus_connect_irq(sbd, 0, pic[irqs[i]]); + g_free(name); + } +} + static void versal_create_usbs(Versal *s, qemu_irq *pic) { DeviceState *dev; @@ -718,6 +750,7 @@ static void versal_realize(DeviceState *dev, Error **errp) versal_create_apu_gic(s, pic); versal_create_rpu_cpus(s); versal_create_uarts(s, pic); + versal_create_canfds(s, pic); versal_create_usbs(s, pic); versal_create_gems(s, pic); versal_create_admas(s, pic); @@ -757,6 +790,10 @@ static void versal_init(Object *obj) static Property versal_properties[] = { DEFINE_PROP_LINK("ddr", Versal, cfg.mr_ddr, TYPE_MEMORY_REGION, MemoryRegion *), + DEFINE_PROP_LINK("canbus0", Versal, lpd.iou.canbus[0], + TYPE_CAN_BUS, CanBusState *), + DEFINE_PROP_LINK("canbus1", Versal, lpd.iou.canbus[1], + TYPE_CAN_BUS, CanBusState *), DEFINE_PROP_END_OF_LIST() }; diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index 2ef5781..e4c2149 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -170,13 +170,16 @@ config VIRT_CTRL config LASI bool +config ALLWINNER_SRAMC + bool + config ALLWINNER_A10_CCM bool config ALLWINNER_A10_DRAMC bool -config AXP209_PMU +config AXP2XX_PMU bool depends on I2C diff --git a/hw/misc/allwinner-r40-ccu.c b/hw/misc/allwinner-r40-ccu.c new file mode 100644 index 0000000..d82fee1 --- /dev/null +++ b/hw/misc/allwinner-r40-ccu.c @@ -0,0 +1,209 @@ +/* + * Allwinner R40 Clock Control Unit emulation + * + * Copyright (C) 2023 qianfan Zhao <qianfanguijin@163.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "hw/sysbus.h" +#include "migration/vmstate.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "hw/misc/allwinner-r40-ccu.h" + +/* CCU register offsets */ +enum { + REG_PLL_CPUX_CTRL = 0x0000, + REG_PLL_AUDIO_CTRL = 0x0008, + REG_PLL_VIDEO0_CTRL = 0x0010, + REG_PLL_VE_CTRL = 0x0018, + REG_PLL_DDR0_CTRL = 0x0020, + REG_PLL_PERIPH0_CTRL = 0x0028, + REG_PLL_PERIPH1_CTRL = 0x002c, + REG_PLL_VIDEO1_CTRL = 0x0030, + REG_PLL_SATA_CTRL = 0x0034, + REG_PLL_GPU_CTRL = 0x0038, + REG_PLL_MIPI_CTRL = 0x0040, + REG_PLL_DE_CTRL = 0x0048, + REG_PLL_DDR1_CTRL = 0x004c, + REG_AHB1_APB1_CFG = 0x0054, + REG_APB2_CFG = 0x0058, + REG_MMC0_CLK = 0x0088, + REG_MMC1_CLK = 0x008c, + REG_MMC2_CLK = 0x0090, + REG_MMC3_CLK = 0x0094, + REG_USBPHY_CFG = 0x00cc, + REG_PLL_DDR_AUX = 0x00f0, + REG_DRAM_CFG = 0x00f4, + REG_PLL_DDR1_CFG = 0x00f8, + REG_DRAM_CLK_GATING = 0x0100, + REG_GMAC_CLK = 0x0164, + REG_SYS_32K_CLK = 0x0310, + REG_PLL_LOCK_CTRL = 0x0320, +}; + +#define REG_INDEX(offset) (offset / sizeof(uint32_t)) + +/* CCU register flags */ +enum { + REG_PLL_ENABLE = (1 << 31), + REG_PLL_LOCK = (1 << 28), +}; + +static uint64_t allwinner_r40_ccu_read(void *opaque, hwaddr offset, + unsigned size) +{ + const AwR40ClockCtlState *s = AW_R40_CCU(opaque); + const uint32_t idx = REG_INDEX(offset); + + switch (offset) { + case 0x324 ... AW_R40_CCU_IOSIZE: + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", + __func__, (uint32_t)offset); + return 0; + } + + return s->regs[idx]; +} + +static void allwinner_r40_ccu_write(void *opaque, hwaddr offset, + uint64_t val, unsigned size) +{ + AwR40ClockCtlState *s = AW_R40_CCU(opaque); + + switch (offset) { + case REG_DRAM_CFG: /* DRAM Configuration(for DDR0) */ + /* bit16: SDRCLK_UPD (SDRCLK configuration 0 update) */ + val &= ~(1 << 16); + break; + case REG_PLL_DDR1_CTRL: /* DDR1 Control register */ + /* bit30: SDRPLL_UPD */ + val &= ~(1 << 30); + if (val & REG_PLL_ENABLE) { + val |= REG_PLL_LOCK; + } + break; + case REG_PLL_CPUX_CTRL: + case REG_PLL_AUDIO_CTRL: + case REG_PLL_VE_CTRL: + case REG_PLL_VIDEO0_CTRL: + case REG_PLL_DDR0_CTRL: + case REG_PLL_PERIPH0_CTRL: + case REG_PLL_PERIPH1_CTRL: + case REG_PLL_VIDEO1_CTRL: + case REG_PLL_SATA_CTRL: + case REG_PLL_GPU_CTRL: + case REG_PLL_MIPI_CTRL: + case REG_PLL_DE_CTRL: + if (val & REG_PLL_ENABLE) { + val |= REG_PLL_LOCK; + } + break; + case 0x324 ... AW_R40_CCU_IOSIZE: + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", + __func__, (uint32_t)offset); + break; + default: + qemu_log_mask(LOG_UNIMP, "%s: unimplemented write offset 0x%04x\n", + __func__, (uint32_t)offset); + break; + } + + s->regs[REG_INDEX(offset)] = (uint32_t) val; +} + +static const MemoryRegionOps allwinner_r40_ccu_ops = { + .read = allwinner_r40_ccu_read, + .write = allwinner_r40_ccu_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .impl.min_access_size = 4, +}; + +static void allwinner_r40_ccu_reset(DeviceState *dev) +{ + AwR40ClockCtlState *s = AW_R40_CCU(dev); + + memset(s->regs, 0, sizeof(s->regs)); + + /* Set default values for registers */ + s->regs[REG_INDEX(REG_PLL_CPUX_CTRL)] = 0x00001000; + s->regs[REG_INDEX(REG_PLL_AUDIO_CTRL)] = 0x00035514; + s->regs[REG_INDEX(REG_PLL_VIDEO0_CTRL)] = 0x03006207; + s->regs[REG_INDEX(REG_PLL_VE_CTRL)] = 0x03006207; + s->regs[REG_INDEX(REG_PLL_DDR0_CTRL)] = 0x00001000, + s->regs[REG_INDEX(REG_PLL_PERIPH0_CTRL)] = 0x00041811; + s->regs[REG_INDEX(REG_PLL_PERIPH1_CTRL)] = 0x00041811; + s->regs[REG_INDEX(REG_PLL_VIDEO1_CTRL)] = 0x03006207; + s->regs[REG_INDEX(REG_PLL_SATA_CTRL)] = 0x00001811; + s->regs[REG_INDEX(REG_PLL_GPU_CTRL)] = 0x03006207; + s->regs[REG_INDEX(REG_PLL_MIPI_CTRL)] = 0x00000515; + s->regs[REG_INDEX(REG_PLL_DE_CTRL)] = 0x03006207; + s->regs[REG_INDEX(REG_PLL_DDR1_CTRL)] = 0x00001800; + s->regs[REG_INDEX(REG_AHB1_APB1_CFG)] = 0x00001010; + s->regs[REG_INDEX(REG_APB2_CFG)] = 0x01000000; + s->regs[REG_INDEX(REG_PLL_DDR_AUX)] = 0x00000001; + s->regs[REG_INDEX(REG_PLL_DDR1_CFG)] = 0x0ccca000; + s->regs[REG_INDEX(REG_SYS_32K_CLK)] = 0x0000000f; +} + +static void allwinner_r40_ccu_init(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + AwR40ClockCtlState *s = AW_R40_CCU(obj); + + /* Memory mapping */ + memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_r40_ccu_ops, s, + TYPE_AW_R40_CCU, AW_R40_CCU_IOSIZE); + sysbus_init_mmio(sbd, &s->iomem); +} + +static const VMStateDescription allwinner_r40_ccu_vmstate = { + .name = "allwinner-r40-ccu", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, AwR40ClockCtlState, AW_R40_CCU_REGS_NUM), + VMSTATE_END_OF_LIST() + } +}; + +static void allwinner_r40_ccu_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = allwinner_r40_ccu_reset; + dc->vmsd = &allwinner_r40_ccu_vmstate; +} + +static const TypeInfo allwinner_r40_ccu_info = { + .name = TYPE_AW_R40_CCU, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_init = allwinner_r40_ccu_init, + .instance_size = sizeof(AwR40ClockCtlState), + .class_init = allwinner_r40_ccu_class_init, +}; + +static void allwinner_r40_ccu_register(void) +{ + type_register_static(&allwinner_r40_ccu_info); +} + +type_init(allwinner_r40_ccu_register) diff --git a/hw/misc/allwinner-r40-dramc.c b/hw/misc/allwinner-r40-dramc.c new file mode 100644 index 0000000..ea61247 --- /dev/null +++ b/hw/misc/allwinner-r40-dramc.c @@ -0,0 +1,513 @@ +/* + * Allwinner R40 SDRAM Controller emulation + * + * CCopyright (C) 2023 qianfan Zhao <qianfanguijin@163.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qemu/error-report.h" +#include "hw/sysbus.h" +#include "migration/vmstate.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "exec/address-spaces.h" +#include "hw/qdev-properties.h" +#include "qapi/error.h" +#include "qemu/bitops.h" +#include "hw/misc/allwinner-r40-dramc.h" +#include "trace.h" + +#define REG_INDEX(offset) (offset / sizeof(uint32_t)) + +/* DRAMCOM register offsets */ +enum { + REG_DRAMCOM_CR = 0x0000, /* Control Register */ +}; + +/* DRAMCOMM register flags */ +enum { + REG_DRAMCOM_CR_DUAL_RANK = (1 << 0), +}; + +/* DRAMCTL register offsets */ +enum { + REG_DRAMCTL_PIR = 0x0000, /* PHY Initialization Register */ + REG_DRAMCTL_PGSR = 0x0010, /* PHY General Status Register */ + REG_DRAMCTL_STATR = 0x0018, /* Status Register */ + REG_DRAMCTL_PGCR = 0x0100, /* PHY general configuration registers */ +}; + +/* DRAMCTL register flags */ +enum { + REG_DRAMCTL_PGSR_INITDONE = (1 << 0), + REG_DRAMCTL_PGSR_READ_TIMEOUT = (1 << 13), + REG_DRAMCTL_PGCR_ENABLE_READ_TIMEOUT = (1 << 25), +}; + +enum { + REG_DRAMCTL_STATR_ACTIVE = (1 << 0), +}; + +#define DRAM_MAX_ROW_BITS 16 +#define DRAM_MAX_COL_BITS 13 /* 8192 */ +#define DRAM_MAX_BANK 3 + +static uint64_t dram_autodetect_cells[DRAM_MAX_ROW_BITS] + [DRAM_MAX_BANK] + [DRAM_MAX_COL_BITS]; +struct VirtualDDRChip { + uint32_t ram_size; + uint8_t bank_bits; + uint8_t row_bits; + uint8_t col_bits; +}; + +/* + * Only power of 2 RAM sizes from 256MiB up to 2048MiB are supported, + * 2GiB memory is not supported due to dual rank feature. + */ +static const struct VirtualDDRChip dummy_ddr_chips[] = { + { + .ram_size = 256, + .bank_bits = 3, + .row_bits = 12, + .col_bits = 13, + }, { + .ram_size = 512, + .bank_bits = 3, + .row_bits = 13, + .col_bits = 13, + }, { + .ram_size = 1024, + .bank_bits = 3, + .row_bits = 14, + .col_bits = 13, + }, { + 0 + } +}; + +static const struct VirtualDDRChip *get_match_ddr(uint32_t ram_size) +{ + const struct VirtualDDRChip *ddr; + + for (ddr = &dummy_ddr_chips[0]; ddr->ram_size; ddr++) { + if (ddr->ram_size == ram_size) { + return ddr; + } + } + + return NULL; +} + +static uint64_t *address_to_autodetect_cells(AwR40DramCtlState *s, + const struct VirtualDDRChip *ddr, + uint32_t offset) +{ + int row_index = 0, bank_index = 0, col_index = 0; + uint32_t row_addr, bank_addr, col_addr; + + row_addr = extract32(offset, s->set_col_bits + s->set_bank_bits, + s->set_row_bits); + bank_addr = extract32(offset, s->set_col_bits, s->set_bank_bits); + col_addr = extract32(offset, 0, s->set_col_bits); + + for (int i = 0; i < ddr->row_bits; i++) { + if (row_addr & BIT(i)) { + row_index = i; + } + } + + for (int i = 0; i < ddr->bank_bits; i++) { + if (bank_addr & BIT(i)) { + bank_index = i; + } + } + + for (int i = 0; i < ddr->col_bits; i++) { + if (col_addr & BIT(i)) { + col_index = i; + } + } + + trace_allwinner_r40_dramc_offset_to_cell(offset, row_index, bank_index, + col_index); + return &dram_autodetect_cells[row_index][bank_index][col_index]; +} + +static void allwinner_r40_dramc_map_rows(AwR40DramCtlState *s, uint8_t row_bits, + uint8_t bank_bits, uint8_t col_bits) +{ + const struct VirtualDDRChip *ddr = get_match_ddr(s->ram_size); + bool enable_detect_cells; + + trace_allwinner_r40_dramc_map_rows(row_bits, bank_bits, col_bits); + + if (!ddr) { + return; + } + + s->set_row_bits = row_bits; + s->set_bank_bits = bank_bits; + s->set_col_bits = col_bits; + + enable_detect_cells = ddr->bank_bits != bank_bits + || ddr->row_bits != row_bits + || ddr->col_bits != col_bits; + + if (enable_detect_cells) { + trace_allwinner_r40_dramc_detect_cells_enable(); + } else { + trace_allwinner_r40_dramc_detect_cells_disable(); + } + + memory_region_set_enabled(&s->detect_cells, enable_detect_cells); +} + +static uint64_t allwinner_r40_dramcom_read(void *opaque, hwaddr offset, + unsigned size) +{ + const AwR40DramCtlState *s = AW_R40_DRAMC(opaque); + const uint32_t idx = REG_INDEX(offset); + + if (idx >= AW_R40_DRAMCOM_REGS_NUM) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", + __func__, (uint32_t)offset); + return 0; + } + + trace_allwinner_r40_dramcom_read(offset, s->dramcom[idx], size); + return s->dramcom[idx]; +} + +static void allwinner_r40_dramcom_write(void *opaque, hwaddr offset, + uint64_t val, unsigned size) +{ + AwR40DramCtlState *s = AW_R40_DRAMC(opaque); + const uint32_t idx = REG_INDEX(offset); + + trace_allwinner_r40_dramcom_write(offset, val, size); + + if (idx >= AW_R40_DRAMCOM_REGS_NUM) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", + __func__, (uint32_t)offset); + return; + } + + switch (offset) { + case REG_DRAMCOM_CR: /* Control Register */ + if (!(val & REG_DRAMCOM_CR_DUAL_RANK)) { + allwinner_r40_dramc_map_rows(s, ((val >> 4) & 0xf) + 1, + ((val >> 2) & 0x1) + 2, + (((val >> 8) & 0xf) + 3)); + } + break; + }; + + s->dramcom[idx] = (uint32_t) val; +} + +static uint64_t allwinner_r40_dramctl_read(void *opaque, hwaddr offset, + unsigned size) +{ + const AwR40DramCtlState *s = AW_R40_DRAMC(opaque); + const uint32_t idx = REG_INDEX(offset); + + if (idx >= AW_R40_DRAMCTL_REGS_NUM) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", + __func__, (uint32_t)offset); + return 0; + } + + trace_allwinner_r40_dramctl_read(offset, s->dramctl[idx], size); + return s->dramctl[idx]; +} + +static void allwinner_r40_dramctl_write(void *opaque, hwaddr offset, + uint64_t val, unsigned size) +{ + AwR40DramCtlState *s = AW_R40_DRAMC(opaque); + const uint32_t idx = REG_INDEX(offset); + + trace_allwinner_r40_dramctl_write(offset, val, size); + + if (idx >= AW_R40_DRAMCTL_REGS_NUM) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", + __func__, (uint32_t)offset); + return; + } + + switch (offset) { + case REG_DRAMCTL_PIR: /* PHY Initialization Register */ + s->dramctl[REG_INDEX(REG_DRAMCTL_PGSR)] |= REG_DRAMCTL_PGSR_INITDONE; + s->dramctl[REG_INDEX(REG_DRAMCTL_STATR)] |= REG_DRAMCTL_STATR_ACTIVE; + break; + } + + s->dramctl[idx] = (uint32_t) val; +} + +static uint64_t allwinner_r40_dramphy_read(void *opaque, hwaddr offset, + unsigned size) +{ + const AwR40DramCtlState *s = AW_R40_DRAMC(opaque); + const uint32_t idx = REG_INDEX(offset); + + if (idx >= AW_R40_DRAMPHY_REGS_NUM) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", + __func__, (uint32_t)offset); + return 0; + } + + trace_allwinner_r40_dramphy_read(offset, s->dramphy[idx], size); + return s->dramphy[idx]; +} + +static void allwinner_r40_dramphy_write(void *opaque, hwaddr offset, + uint64_t val, unsigned size) +{ + AwR40DramCtlState *s = AW_R40_DRAMC(opaque); + const uint32_t idx = REG_INDEX(offset); + + trace_allwinner_r40_dramphy_write(offset, val, size); + + if (idx >= AW_R40_DRAMPHY_REGS_NUM) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", + __func__, (uint32_t)offset); + return; + } + + s->dramphy[idx] = (uint32_t) val; +} + +static const MemoryRegionOps allwinner_r40_dramcom_ops = { + .read = allwinner_r40_dramcom_read, + .write = allwinner_r40_dramcom_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .impl.min_access_size = 4, +}; + +static const MemoryRegionOps allwinner_r40_dramctl_ops = { + .read = allwinner_r40_dramctl_read, + .write = allwinner_r40_dramctl_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .impl.min_access_size = 4, +}; + +static const MemoryRegionOps allwinner_r40_dramphy_ops = { + .read = allwinner_r40_dramphy_read, + .write = allwinner_r40_dramphy_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .impl.min_access_size = 4, +}; + +static uint64_t allwinner_r40_detect_read(void *opaque, hwaddr offset, + unsigned size) +{ + AwR40DramCtlState *s = AW_R40_DRAMC(opaque); + const struct VirtualDDRChip *ddr = get_match_ddr(s->ram_size); + uint64_t data = 0; + + if (ddr) { + data = *address_to_autodetect_cells(s, ddr, (uint32_t)offset); + } + + trace_allwinner_r40_dramc_detect_cell_read(offset, data); + return data; +} + +static void allwinner_r40_detect_write(void *opaque, hwaddr offset, + uint64_t data, unsigned size) +{ + AwR40DramCtlState *s = AW_R40_DRAMC(opaque); + const struct VirtualDDRChip *ddr = get_match_ddr(s->ram_size); + + if (ddr) { + uint64_t *cell = address_to_autodetect_cells(s, ddr, (uint32_t)offset); + trace_allwinner_r40_dramc_detect_cell_write(offset, data); + *cell = data; + } +} + +static const MemoryRegionOps allwinner_r40_detect_ops = { + .read = allwinner_r40_detect_read, + .write = allwinner_r40_detect_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .impl.min_access_size = 4, +}; + +/* + * mctl_r40_detect_rank_count in u-boot will write the high 1G of DDR + * to detect wether the board support dual_rank or not. Create a virtual memory + * if the board's ram_size less or equal than 1G, and set read time out flag of + * REG_DRAMCTL_PGSR when the user touch this high dram. + */ +static uint64_t allwinner_r40_dualrank_detect_read(void *opaque, hwaddr offset, + unsigned size) +{ + AwR40DramCtlState *s = AW_R40_DRAMC(opaque); + uint32_t reg; + + reg = s->dramctl[REG_INDEX(REG_DRAMCTL_PGCR)]; + if (reg & REG_DRAMCTL_PGCR_ENABLE_READ_TIMEOUT) { /* Enable read time out */ + /* + * this driver only support one rank, mark READ_TIMEOUT when try + * read the second rank. + */ + s->dramctl[REG_INDEX(REG_DRAMCTL_PGSR)] + |= REG_DRAMCTL_PGSR_READ_TIMEOUT; + } + + return 0; +} + +static const MemoryRegionOps allwinner_r40_dualrank_detect_ops = { + .read = allwinner_r40_dualrank_detect_read, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .impl.min_access_size = 4, +}; + +static void allwinner_r40_dramc_reset(DeviceState *dev) +{ + AwR40DramCtlState *s = AW_R40_DRAMC(dev); + + /* Set default values for registers */ + memset(&s->dramcom, 0, sizeof(s->dramcom)); + memset(&s->dramctl, 0, sizeof(s->dramctl)); + memset(&s->dramphy, 0, sizeof(s->dramphy)); +} + +static void allwinner_r40_dramc_realize(DeviceState *dev, Error **errp) +{ + AwR40DramCtlState *s = AW_R40_DRAMC(dev); + + if (!get_match_ddr(s->ram_size)) { + error_report("%s: ram-size %u MiB is not supported", + __func__, s->ram_size); + exit(1); + } + + /* detect_cells */ + sysbus_mmio_map_overlap(SYS_BUS_DEVICE(s), 3, s->ram_addr, 10); + memory_region_set_enabled(&s->detect_cells, false); + + /* + * We only support DRAM size up to 1G now, so prepare a high memory page + * after 1G for dualrank detect. index = 4 + */ + memory_region_init_io(&s->dram_high, OBJECT(s), + &allwinner_r40_dualrank_detect_ops, s, + "DRAMHIGH", KiB); + sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->dram_high); + sysbus_mmio_map(SYS_BUS_DEVICE(s), 4, s->ram_addr + GiB); +} + +static void allwinner_r40_dramc_init(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + AwR40DramCtlState *s = AW_R40_DRAMC(obj); + + /* DRAMCOM registers, index 0 */ + memory_region_init_io(&s->dramcom_iomem, OBJECT(s), + &allwinner_r40_dramcom_ops, s, + "DRAMCOM", 4 * KiB); + sysbus_init_mmio(sbd, &s->dramcom_iomem); + + /* DRAMCTL registers, index 1 */ + memory_region_init_io(&s->dramctl_iomem, OBJECT(s), + &allwinner_r40_dramctl_ops, s, + "DRAMCTL", 4 * KiB); + sysbus_init_mmio(sbd, &s->dramctl_iomem); + + /* DRAMPHY registers. index 2 */ + memory_region_init_io(&s->dramphy_iomem, OBJECT(s), + &allwinner_r40_dramphy_ops, s, + "DRAMPHY", 4 * KiB); + sysbus_init_mmio(sbd, &s->dramphy_iomem); + + /* R40 support max 2G memory but we only support up to 1G now. index 3 */ + memory_region_init_io(&s->detect_cells, OBJECT(s), + &allwinner_r40_detect_ops, s, + "DRAMCELLS", 1 * GiB); + sysbus_init_mmio(sbd, &s->detect_cells); +} + +static Property allwinner_r40_dramc_properties[] = { + DEFINE_PROP_UINT64("ram-addr", AwR40DramCtlState, ram_addr, 0x0), + DEFINE_PROP_UINT32("ram-size", AwR40DramCtlState, ram_size, 256), /* MiB */ + DEFINE_PROP_END_OF_LIST() +}; + +static const VMStateDescription allwinner_r40_dramc_vmstate = { + .name = "allwinner-r40-dramc", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(dramcom, AwR40DramCtlState, + AW_R40_DRAMCOM_REGS_NUM), + VMSTATE_UINT32_ARRAY(dramctl, AwR40DramCtlState, + AW_R40_DRAMCTL_REGS_NUM), + VMSTATE_UINT32_ARRAY(dramphy, AwR40DramCtlState, + AW_R40_DRAMPHY_REGS_NUM), + VMSTATE_END_OF_LIST() + } +}; + +static void allwinner_r40_dramc_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = allwinner_r40_dramc_reset; + dc->vmsd = &allwinner_r40_dramc_vmstate; + dc->realize = allwinner_r40_dramc_realize; + device_class_set_props(dc, allwinner_r40_dramc_properties); +} + +static const TypeInfo allwinner_r40_dramc_info = { + .name = TYPE_AW_R40_DRAMC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_init = allwinner_r40_dramc_init, + .instance_size = sizeof(AwR40DramCtlState), + .class_init = allwinner_r40_dramc_class_init, +}; + +static void allwinner_r40_dramc_register(void) +{ + type_register_static(&allwinner_r40_dramc_info); +} + +type_init(allwinner_r40_dramc_register) diff --git a/hw/misc/allwinner-sramc.c b/hw/misc/allwinner-sramc.c new file mode 100644 index 0000000..a8b731f --- /dev/null +++ b/hw/misc/allwinner-sramc.c @@ -0,0 +1,184 @@ +/* + * Allwinner R40 SRAM controller emulation + * + * Copyright (C) 2023 qianfan Zhao <qianfanguijin@163.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "hw/sysbus.h" +#include "migration/vmstate.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qapi/error.h" +#include "hw/qdev-properties.h" +#include "hw/qdev-properties-system.h" +#include "hw/misc/allwinner-sramc.h" +#include "trace.h" + +/* + * register offsets + * https://linux-sunxi.org/SRAM_Controller_Register_Guide + */ +enum { + REG_SRAM_CTL1_CFG = 0x04, /* SRAM Control register 1 */ + REG_SRAM_VER = 0x24, /* SRAM Version register */ + REG_SRAM_R40_SOFT_ENTRY_REG0 = 0xbc, +}; + +/* REG_SRAMC_VERSION bit defines */ +#define SRAM_VER_READ_ENABLE (1 << 15) +#define SRAM_VER_VERSION_SHIFT 16 +#define SRAM_VERSION_SUN8I_R40 0x1701 + +static uint64_t allwinner_sramc_read(void *opaque, hwaddr offset, + unsigned size) +{ + AwSRAMCState *s = AW_SRAMC(opaque); + AwSRAMCClass *sc = AW_SRAMC_GET_CLASS(s); + uint64_t val = 0; + + switch (offset) { + case REG_SRAM_CTL1_CFG: + val = s->sram_ctl1; + break; + case REG_SRAM_VER: + /* bit15: lock bit, set this bit before reading this register */ + if (s->sram_ver & SRAM_VER_READ_ENABLE) { + val = SRAM_VER_READ_ENABLE | + (sc->sram_version_code << SRAM_VER_VERSION_SHIFT); + } + break; + case REG_SRAM_R40_SOFT_ENTRY_REG0: + val = s->sram_soft_entry_reg0; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", + __func__, (uint32_t)offset); + return 0; + } + + trace_allwinner_sramc_read(offset, val); + + return val; +} + +static void allwinner_sramc_write(void *opaque, hwaddr offset, + uint64_t val, unsigned size) +{ + AwSRAMCState *s = AW_SRAMC(opaque); + + trace_allwinner_sramc_write(offset, val); + + switch (offset) { + case REG_SRAM_CTL1_CFG: + s->sram_ctl1 = val; + break; + case REG_SRAM_VER: + /* Only the READ_ENABLE bit is writeable */ + s->sram_ver = val & SRAM_VER_READ_ENABLE; + break; + case REG_SRAM_R40_SOFT_ENTRY_REG0: + s->sram_soft_entry_reg0 = val; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n", + __func__, (uint32_t)offset); + break; + } +} + +static const MemoryRegionOps allwinner_sramc_ops = { + .read = allwinner_sramc_read, + .write = allwinner_sramc_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, + .impl.min_access_size = 4, +}; + +static const VMStateDescription allwinner_sramc_vmstate = { + .name = "allwinner-sramc", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(sram_ver, AwSRAMCState), + VMSTATE_UINT32(sram_soft_entry_reg0, AwSRAMCState), + VMSTATE_END_OF_LIST() + } +}; + +static void allwinner_sramc_reset(DeviceState *dev) +{ + AwSRAMCState *s = AW_SRAMC(dev); + AwSRAMCClass *sc = AW_SRAMC_GET_CLASS(s); + + switch (sc->sram_version_code) { + case SRAM_VERSION_SUN8I_R40: + s->sram_ctl1 = 0x1300; + break; + } +} + +static void allwinner_sramc_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = allwinner_sramc_reset; + dc->vmsd = &allwinner_sramc_vmstate; +} + +static void allwinner_sramc_init(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + AwSRAMCState *s = AW_SRAMC(obj); + + /* Memory mapping */ + memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_sramc_ops, s, + TYPE_AW_SRAMC, 1 * KiB); + sysbus_init_mmio(sbd, &s->iomem); +} + +static const TypeInfo allwinner_sramc_info = { + .name = TYPE_AW_SRAMC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_init = allwinner_sramc_init, + .instance_size = sizeof(AwSRAMCState), + .class_init = allwinner_sramc_class_init, +}; + +static void allwinner_r40_sramc_class_init(ObjectClass *klass, void *data) +{ + AwSRAMCClass *sc = AW_SRAMC_CLASS(klass); + + sc->sram_version_code = SRAM_VERSION_SUN8I_R40; +} + +static const TypeInfo allwinner_r40_sramc_info = { + .name = TYPE_AW_SRAMC_SUN8I_R40, + .parent = TYPE_AW_SRAMC, + .class_init = allwinner_r40_sramc_class_init, +}; + +static void allwinner_sramc_register(void) +{ + type_register_static(&allwinner_sramc_info); + type_register_static(&allwinner_r40_sramc_info); +} + +type_init(allwinner_sramc_register) diff --git a/hw/misc/axp209.c b/hw/misc/axp209.c deleted file mode 100644 index 2908ed9..0000000 --- a/hw/misc/axp209.c +++ /dev/null @@ -1,238 +0,0 @@ -/* - * AXP-209 PMU Emulation - * - * Copyright (C) 2022 Strahinja Jankovic <strahinja.p.jankovic@gmail.com> - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - */ - -#include "qemu/osdep.h" -#include "qemu/log.h" -#include "trace.h" -#include "hw/i2c/i2c.h" -#include "migration/vmstate.h" - -#define TYPE_AXP209_PMU "axp209_pmu" - -#define AXP209(obj) \ - OBJECT_CHECK(AXP209I2CState, (obj), TYPE_AXP209_PMU) - -/* registers */ -enum { - REG_POWER_STATUS = 0x0u, - REG_OPERATING_MODE, - REG_OTG_VBUS_STATUS, - REG_CHIP_VERSION, - REG_DATA_CACHE_0, - REG_DATA_CACHE_1, - REG_DATA_CACHE_2, - REG_DATA_CACHE_3, - REG_DATA_CACHE_4, - REG_DATA_CACHE_5, - REG_DATA_CACHE_6, - REG_DATA_CACHE_7, - REG_DATA_CACHE_8, - REG_DATA_CACHE_9, - REG_DATA_CACHE_A, - REG_DATA_CACHE_B, - REG_POWER_OUTPUT_CTRL = 0x12u, - REG_DC_DC2_OUT_V_CTRL = 0x23u, - REG_DC_DC2_DVS_CTRL = 0x25u, - REG_DC_DC3_OUT_V_CTRL = 0x27u, - REG_LDO2_4_OUT_V_CTRL, - REG_LDO3_OUT_V_CTRL, - REG_VBUS_CH_MGMT = 0x30u, - REG_SHUTDOWN_V_CTRL, - REG_SHUTDOWN_CTRL, - REG_CHARGE_CTRL_1, - REG_CHARGE_CTRL_2, - REG_SPARE_CHARGE_CTRL, - REG_PEK_KEY_CTRL, - REG_DC_DC_FREQ_SET, - REG_CHR_TEMP_TH_SET, - REG_CHR_HIGH_TEMP_TH_CTRL, - REG_IPSOUT_WARN_L1, - REG_IPSOUT_WARN_L2, - REG_DISCHR_TEMP_TH_SET, - REG_DISCHR_HIGH_TEMP_TH_CTRL, - REG_IRQ_BANK_1_CTRL = 0x40u, - REG_IRQ_BANK_2_CTRL, - REG_IRQ_BANK_3_CTRL, - REG_IRQ_BANK_4_CTRL, - REG_IRQ_BANK_5_CTRL, - REG_IRQ_BANK_1_STAT = 0x48u, - REG_IRQ_BANK_2_STAT, - REG_IRQ_BANK_3_STAT, - REG_IRQ_BANK_4_STAT, - REG_IRQ_BANK_5_STAT, - REG_ADC_ACIN_V_H = 0x56u, - REG_ADC_ACIN_V_L, - REG_ADC_ACIN_CURR_H, - REG_ADC_ACIN_CURR_L, - REG_ADC_VBUS_V_H, - REG_ADC_VBUS_V_L, - REG_ADC_VBUS_CURR_H, - REG_ADC_VBUS_CURR_L, - REG_ADC_INT_TEMP_H, - REG_ADC_INT_TEMP_L, - REG_ADC_TEMP_SENS_V_H = 0x62u, - REG_ADC_TEMP_SENS_V_L, - REG_ADC_BAT_V_H = 0x78u, - REG_ADC_BAT_V_L, - REG_ADC_BAT_DISCHR_CURR_H, - REG_ADC_BAT_DISCHR_CURR_L, - REG_ADC_BAT_CHR_CURR_H, - REG_ADC_BAT_CHR_CURR_L, - REG_ADC_IPSOUT_V_H, - REG_ADC_IPSOUT_V_L, - REG_DC_DC_MOD_SEL = 0x80u, - REG_ADC_EN_1, - REG_ADC_EN_2, - REG_ADC_SR_CTRL, - REG_ADC_IN_RANGE, - REG_GPIO1_ADC_IRQ_RISING_TH, - REG_GPIO1_ADC_IRQ_FALLING_TH, - REG_TIMER_CTRL = 0x8au, - REG_VBUS_CTRL_MON_SRP, - REG_OVER_TEMP_SHUTDOWN = 0x8fu, - REG_GPIO0_FEAT_SET, - REG_GPIO_OUT_HIGH_SET, - REG_GPIO1_FEAT_SET, - REG_GPIO2_FEAT_SET, - REG_GPIO_SIG_STATE_SET_MON, - REG_GPIO3_SET, - REG_COULOMB_CNTR_CTRL = 0xb8u, - REG_POWER_MEAS_RES, - NR_REGS -}; - -#define AXP209_CHIP_VERSION_ID (0x01) -#define AXP209_DC_DC2_OUT_V_CTRL_RESET (0x16) -#define AXP209_IRQ_BANK_1_CTRL_RESET (0xd8) - -/* A simple I2C slave which returns values of ID or CNT register. */ -typedef struct AXP209I2CState { - /*< private >*/ - I2CSlave i2c; - /*< public >*/ - uint8_t regs[NR_REGS]; /* peripheral registers */ - uint8_t ptr; /* current register index */ - uint8_t count; /* counter used for tx/rx */ -} AXP209I2CState; - -/* Reset all counters and load ID register */ -static void axp209_reset_enter(Object *obj, ResetType type) -{ - AXP209I2CState *s = AXP209(obj); - - memset(s->regs, 0, NR_REGS); - s->ptr = 0; - s->count = 0; - s->regs[REG_CHIP_VERSION] = AXP209_CHIP_VERSION_ID; - s->regs[REG_DC_DC2_OUT_V_CTRL] = AXP209_DC_DC2_OUT_V_CTRL_RESET; - s->regs[REG_IRQ_BANK_1_CTRL] = AXP209_IRQ_BANK_1_CTRL_RESET; -} - -/* Handle events from master. */ -static int axp209_event(I2CSlave *i2c, enum i2c_event event) -{ - AXP209I2CState *s = AXP209(i2c); - - s->count = 0; - - return 0; -} - -/* Called when master requests read */ -static uint8_t axp209_rx(I2CSlave *i2c) -{ - AXP209I2CState *s = AXP209(i2c); - uint8_t ret = 0xff; - - if (s->ptr < NR_REGS) { - ret = s->regs[s->ptr++]; - } - - trace_axp209_rx(s->ptr - 1, ret); - - return ret; -} - -/* - * Called when master sends write. - * Update ptr with byte 0, then perform write with second byte. - */ -static int axp209_tx(I2CSlave *i2c, uint8_t data) -{ - AXP209I2CState *s = AXP209(i2c); - - if (s->count == 0) { - /* Store register address */ - s->ptr = data; - s->count++; - trace_axp209_select(data); - } else { - trace_axp209_tx(s->ptr, data); - if (s->ptr == REG_DC_DC2_OUT_V_CTRL) { - s->regs[s->ptr++] = data; - } - } - - return 0; -} - -static const VMStateDescription vmstate_axp209 = { - .name = TYPE_AXP209_PMU, - .version_id = 1, - .fields = (VMStateField[]) { - VMSTATE_UINT8_ARRAY(regs, AXP209I2CState, NR_REGS), - VMSTATE_UINT8(count, AXP209I2CState), - VMSTATE_UINT8(ptr, AXP209I2CState), - VMSTATE_END_OF_LIST() - } -}; - -static void axp209_class_init(ObjectClass *oc, void *data) -{ - DeviceClass *dc = DEVICE_CLASS(oc); - I2CSlaveClass *isc = I2C_SLAVE_CLASS(oc); - ResettableClass *rc = RESETTABLE_CLASS(oc); - - rc->phases.enter = axp209_reset_enter; - dc->vmsd = &vmstate_axp209; - isc->event = axp209_event; - isc->recv = axp209_rx; - isc->send = axp209_tx; -} - -static const TypeInfo axp209_info = { - .name = TYPE_AXP209_PMU, - .parent = TYPE_I2C_SLAVE, - .instance_size = sizeof(AXP209I2CState), - .class_init = axp209_class_init -}; - -static void axp209_register_devices(void) -{ - type_register_static(&axp209_info); -} - -type_init(axp209_register_devices); diff --git a/hw/misc/axp2xx.c b/hw/misc/axp2xx.c new file mode 100644 index 0000000..41538c1 --- /dev/null +++ b/hw/misc/axp2xx.c @@ -0,0 +1,283 @@ +/* + * AXP-2XX PMU Emulation, supported lists: + * AXP209 + * AXP221 + * + * Copyright (C) 2022 Strahinja Jankovic <strahinja.p.jankovic@gmail.com> + * Copyright (C) 2023 qianfan Zhao <qianfanguijin@163.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qom/object.h" +#include "trace.h" +#include "hw/i2c/i2c.h" +#include "migration/vmstate.h" + +#define TYPE_AXP2XX "axp2xx_pmu" +#define TYPE_AXP209_PMU "axp209_pmu" +#define TYPE_AXP221_PMU "axp221_pmu" + +OBJECT_DECLARE_TYPE(AXP2xxI2CState, AXP2xxClass, AXP2XX) + +#define NR_REGS (0xff) + +/* A simple I2C slave which returns values of ID or CNT register. */ +typedef struct AXP2xxI2CState { + /*< private >*/ + I2CSlave i2c; + /*< public >*/ + uint8_t regs[NR_REGS]; /* peripheral registers */ + uint8_t ptr; /* current register index */ + uint8_t count; /* counter used for tx/rx */ +} AXP2xxI2CState; + +typedef struct AXP2xxClass { + /*< private >*/ + I2CSlaveClass parent_class; + /*< public >*/ + void (*reset_enter)(AXP2xxI2CState *s, ResetType type); +} AXP2xxClass; + +#define AXP209_CHIP_VERSION_ID (0x01) +#define AXP209_DC_DC2_OUT_V_CTRL_RESET (0x16) + +/* Reset all counters and load ID register */ +static void axp209_reset_enter(AXP2xxI2CState *s, ResetType type) +{ + memset(s->regs, 0, NR_REGS); + s->ptr = 0; + s->count = 0; + + s->regs[0x03] = AXP209_CHIP_VERSION_ID; + s->regs[0x23] = AXP209_DC_DC2_OUT_V_CTRL_RESET; + + s->regs[0x30] = 0x60; + s->regs[0x32] = 0x46; + s->regs[0x34] = 0x41; + s->regs[0x35] = 0x22; + s->regs[0x36] = 0x5d; + s->regs[0x37] = 0x08; + s->regs[0x38] = 0xa5; + s->regs[0x39] = 0x1f; + s->regs[0x3a] = 0x68; + s->regs[0x3b] = 0x5f; + s->regs[0x3c] = 0xfc; + s->regs[0x3d] = 0x16; + s->regs[0x40] = 0xd8; + s->regs[0x42] = 0xff; + s->regs[0x43] = 0x3b; + s->regs[0x80] = 0xe0; + s->regs[0x82] = 0x83; + s->regs[0x83] = 0x80; + s->regs[0x84] = 0x32; + s->regs[0x86] = 0xff; + s->regs[0x90] = 0x07; + s->regs[0x91] = 0xa0; + s->regs[0x92] = 0x07; + s->regs[0x93] = 0x07; +} + +#define AXP221_PWR_STATUS_ACIN_PRESENT BIT(7) +#define AXP221_PWR_STATUS_ACIN_AVAIL BIT(6) +#define AXP221_PWR_STATUS_VBUS_PRESENT BIT(5) +#define AXP221_PWR_STATUS_VBUS_USED BIT(4) +#define AXP221_PWR_STATUS_BAT_CHARGING BIT(2) +#define AXP221_PWR_STATUS_ACIN_VBUS_POWERED BIT(1) + +/* Reset all counters and load ID register */ +static void axp221_reset_enter(AXP2xxI2CState *s, ResetType type) +{ + memset(s->regs, 0, NR_REGS); + s->ptr = 0; + s->count = 0; + + /* input power status register */ + s->regs[0x00] = AXP221_PWR_STATUS_ACIN_PRESENT + | AXP221_PWR_STATUS_ACIN_AVAIL + | AXP221_PWR_STATUS_ACIN_VBUS_POWERED; + + s->regs[0x01] = 0x00; /* no battery is connected */ + + /* + * CHIPID register, no documented on datasheet, but it is checked in + * u-boot spl. I had read it from AXP221s and got 0x06 value. + * So leave 06h here. + */ + s->regs[0x03] = 0x06; + + s->regs[0x10] = 0xbf; + s->regs[0x13] = 0x01; + s->regs[0x30] = 0x60; + s->regs[0x31] = 0x03; + s->regs[0x32] = 0x43; + s->regs[0x33] = 0xc6; + s->regs[0x34] = 0x45; + s->regs[0x35] = 0x0e; + s->regs[0x36] = 0x5d; + s->regs[0x37] = 0x08; + s->regs[0x38] = 0xa5; + s->regs[0x39] = 0x1f; + s->regs[0x3c] = 0xfc; + s->regs[0x3d] = 0x16; + s->regs[0x80] = 0x80; + s->regs[0x82] = 0xe0; + s->regs[0x84] = 0x32; + s->regs[0x8f] = 0x01; + + s->regs[0x90] = 0x07; + s->regs[0x91] = 0x1f; + s->regs[0x92] = 0x07; + s->regs[0x93] = 0x1f; + + s->regs[0x40] = 0xd8; + s->regs[0x41] = 0xff; + s->regs[0x42] = 0x03; + s->regs[0x43] = 0x03; + + s->regs[0xb8] = 0xc0; + s->regs[0xb9] = 0x64; + s->regs[0xe6] = 0xa0; +} + +static void axp2xx_reset_enter(Object *obj, ResetType type) +{ + AXP2xxI2CState *s = AXP2XX(obj); + AXP2xxClass *sc = AXP2XX_GET_CLASS(s); + + sc->reset_enter(s, type); +} + +/* Handle events from master. */ +static int axp2xx_event(I2CSlave *i2c, enum i2c_event event) +{ + AXP2xxI2CState *s = AXP2XX(i2c); + + s->count = 0; + + return 0; +} + +/* Called when master requests read */ +static uint8_t axp2xx_rx(I2CSlave *i2c) +{ + AXP2xxI2CState *s = AXP2XX(i2c); + uint8_t ret = 0xff; + + if (s->ptr < NR_REGS) { + ret = s->regs[s->ptr++]; + } + + trace_axp2xx_rx(s->ptr - 1, ret); + + return ret; +} + +/* + * Called when master sends write. + * Update ptr with byte 0, then perform write with second byte. + */ +static int axp2xx_tx(I2CSlave *i2c, uint8_t data) +{ + AXP2xxI2CState *s = AXP2XX(i2c); + + if (s->count == 0) { + /* Store register address */ + s->ptr = data; + s->count++; + trace_axp2xx_select(data); + } else { + trace_axp2xx_tx(s->ptr, data); + s->regs[s->ptr++] = data; + } + + return 0; +} + +static const VMStateDescription vmstate_axp2xx = { + .name = TYPE_AXP2XX, + .version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8_ARRAY(regs, AXP2xxI2CState, NR_REGS), + VMSTATE_UINT8(ptr, AXP2xxI2CState), + VMSTATE_UINT8(count, AXP2xxI2CState), + VMSTATE_END_OF_LIST() + } +}; + +static void axp2xx_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + I2CSlaveClass *isc = I2C_SLAVE_CLASS(oc); + ResettableClass *rc = RESETTABLE_CLASS(oc); + + rc->phases.enter = axp2xx_reset_enter; + dc->vmsd = &vmstate_axp2xx; + isc->event = axp2xx_event; + isc->recv = axp2xx_rx; + isc->send = axp2xx_tx; +} + +static const TypeInfo axp2xx_info = { + .name = TYPE_AXP2XX, + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(AXP2xxI2CState), + .class_size = sizeof(AXP2xxClass), + .class_init = axp2xx_class_init, + .abstract = true, +}; + +static void axp209_class_init(ObjectClass *oc, void *data) +{ + AXP2xxClass *sc = AXP2XX_CLASS(oc); + + sc->reset_enter = axp209_reset_enter; +} + +static const TypeInfo axp209_info = { + .name = TYPE_AXP209_PMU, + .parent = TYPE_AXP2XX, + .class_init = axp209_class_init +}; + +static void axp221_class_init(ObjectClass *oc, void *data) +{ + AXP2xxClass *sc = AXP2XX_CLASS(oc); + + sc->reset_enter = axp221_reset_enter; +} + +static const TypeInfo axp221_info = { + .name = TYPE_AXP221_PMU, + .parent = TYPE_AXP2XX, + .class_init = axp221_class_init, +}; + +static void axp2xx_register_devices(void) +{ + type_register_static(&axp2xx_info); + type_register_static(&axp209_info); + type_register_static(&axp221_info); +} + +type_init(axp2xx_register_devices); diff --git a/hw/misc/meson.build b/hw/misc/meson.build index a40245a..78ca857 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -37,6 +37,7 @@ subdir('macio') softmmu_ss.add(when: 'CONFIG_IVSHMEM_DEVICE', if_true: files('ivshmem.c')) +softmmu_ss.add(when: 'CONFIG_ALLWINNER_SRAMC', if_true: files('allwinner-sramc.c')) softmmu_ss.add(when: 'CONFIG_ALLWINNER_A10_CCM', if_true: files('allwinner-a10-ccm.c')) softmmu_ss.add(when: 'CONFIG_ALLWINNER_A10_DRAMC', if_true: files('allwinner-a10-dramc.c')) softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3-ccu.c')) @@ -44,7 +45,9 @@ specific_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-cpucfg.c' softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3-dramc.c')) softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3-sysctrl.c')) softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-sid.c')) -softmmu_ss.add(when: 'CONFIG_AXP209_PMU', if_true: files('axp209.c')) +softmmu_ss.add(when: 'CONFIG_ALLWINNER_R40', if_true: files('allwinner-r40-ccu.c')) +softmmu_ss.add(when: 'CONFIG_ALLWINNER_R40', if_true: files('allwinner-r40-dramc.c')) +softmmu_ss.add(when: 'CONFIG_AXP2XX_PMU', if_true: files('axp2xx.c')) softmmu_ss.add(when: 'CONFIG_REALVIEW', if_true: files('arm_sysctl.c')) softmmu_ss.add(when: 'CONFIG_NSERIES', if_true: files('cbus.c')) softmmu_ss.add(when: 'CONFIG_ECCMEMCTL', if_true: files('eccmemctl.c')) diff --git a/hw/misc/trace-events b/hw/misc/trace-events index c47876a..4d1a0e1 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -15,18 +15,36 @@ allwinner_h3_dramctl_write(uint64_t offset, uint64_t data, unsigned size) "Write allwinner_h3_dramphy_read(uint64_t offset, uint64_t data, unsigned size) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32 allwinner_h3_dramphy_write(uint64_t offset, uint64_t data, unsigned size) "write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32 +# allwinner-r40-dramc.c +allwinner_r40_dramc_detect_cells_disable(void) "Disable detect cells" +allwinner_r40_dramc_detect_cells_enable(void) "Enable detect cells" +allwinner_r40_dramc_map_rows(uint8_t row_bits, uint8_t bank_bits, uint8_t col_bits) "DRAM layout: row_bits %d, bank_bits %d, col_bits %d" +allwinner_r40_dramc_offset_to_cell(uint64_t offset, int row, int bank, int col) "offset 0x%" PRIx64 " row %d bank %d col %d" +allwinner_r40_dramc_detect_cell_write(uint64_t offset, uint64_t data) "offset 0x%" PRIx64 " data 0x%" PRIx64 "" +allwinner_r40_dramc_detect_cell_read(uint64_t offset, uint64_t data) "offset 0x%" PRIx64 " data 0x%" PRIx64 "" +allwinner_r40_dramcom_read(uint64_t offset, uint64_t data, unsigned size) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32 +allwinner_r40_dramcom_write(uint64_t offset, uint64_t data, unsigned size) "Write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32 +allwinner_r40_dramctl_read(uint64_t offset, uint64_t data, unsigned size) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32 +allwinner_r40_dramctl_write(uint64_t offset, uint64_t data, unsigned size) "Write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32 +allwinner_r40_dramphy_read(uint64_t offset, uint64_t data, unsigned size) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32 +allwinner_r40_dramphy_write(uint64_t offset, uint64_t data, unsigned size) "write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32 + # allwinner-sid.c allwinner_sid_read(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32 allwinner_sid_write(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32 +# allwinner-sramc.c +allwinner_sramc_read(uint64_t offset, uint64_t data) "offset 0x%" PRIx64 " data 0x%" PRIx64 +allwinner_sramc_write(uint64_t offset, uint64_t data) "offset 0x%" PRIx64 " data 0x%" PRIx64 + # avr_power.c avr_power_read(uint8_t value) "power_reduc read value:%u" avr_power_write(uint8_t value) "power_reduc write value:%u" -# axp209.c -axp209_rx(uint8_t reg, uint8_t data) "Read reg 0x%" PRIx8 " : 0x%" PRIx8 -axp209_select(uint8_t reg) "Accessing reg 0x%" PRIx8 -axp209_tx(uint8_t reg, uint8_t data) "Write reg 0x%" PRIx8 " : 0x%" PRIx8 +# axp2xx +axp2xx_rx(uint8_t reg, uint8_t data) "Read reg 0x%" PRIx8 " : 0x%" PRIx8 +axp2xx_select(uint8_t reg) "Accessing reg 0x%" PRIx8 +axp2xx_tx(uint8_t reg, uint8_t data) "Write reg 0x%" PRIx8 " : 0x%" PRIx8 # eccmemctl.c ecc_mem_writel_mer(uint32_t val) "Write memory enable 0x%08x" diff --git a/hw/net/can/meson.build b/hw/net/can/meson.build index 8fabbd9..8d85201 100644 --- a/hw/net/can/meson.build +++ b/hw/net/can/meson.build @@ -5,3 +5,4 @@ softmmu_ss.add(when: 'CONFIG_CAN_PCI', if_true: files('can_mioe3680_pci.c')) softmmu_ss.add(when: 'CONFIG_CAN_CTUCANFD', if_true: files('ctucan_core.c')) softmmu_ss.add(when: 'CONFIG_CAN_CTUCANFD_PCI', if_true: files('ctucan_pci.c')) softmmu_ss.add(when: 'CONFIG_XLNX_ZYNQMP', if_true: files('xlnx-zynqmp-can.c')) +softmmu_ss.add(when: 'CONFIG_XLNX_VERSAL', if_true: files('xlnx-versal-canfd.c')) diff --git a/hw/net/can/trace-events b/hw/net/can/trace-events index 8346a98..de64ac1 100644 --- a/hw/net/can/trace-events +++ b/hw/net/can/trace-events @@ -7,3 +7,10 @@ xlnx_can_filter_mask_pre_write(uint8_t filter_num, uint32_t value) "Filter%d MAS xlnx_can_tx_data(uint32_t id, uint8_t dlc, uint8_t db0, uint8_t db1, uint8_t db2, uint8_t db3, uint8_t db4, uint8_t db5, uint8_t db6, uint8_t db7) "Frame: ID: 0x%08x DLC: 0x%02x DATA: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x" xlnx_can_rx_data(uint32_t id, uint32_t dlc, uint8_t db0, uint8_t db1, uint8_t db2, uint8_t db3, uint8_t db4, uint8_t db5, uint8_t db6, uint8_t db7) "Frame: ID: 0x%08x DLC: 0x%02x DATA: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x" xlnx_can_rx_discard(uint32_t status) "Controller is not enabled for bus communication. Status Register: 0x%08x" + +# xlnx-versal-canfd.c +xlnx_canfd_update_irq(char *path, uint32_t isr, uint32_t ier, uint32_t irq) "%s: ISR: 0x%08x IER: 0x%08x IRQ: 0x%08x" +xlnx_canfd_rx_fifo_filter_reject(char *path, uint32_t id, uint8_t dlc) "%s: Frame: ID: 0x%08x DLC: 0x%02x" +xlnx_canfd_rx_data(char *path, uint32_t id, uint8_t dlc, uint8_t flags) "%s: Frame: ID: 0x%08x DLC: 0x%02x CANFD Flag: 0x%02x" +xlnx_canfd_tx_data(char *path, uint32_t id, uint8_t dlc, uint8_t flgas) "%s: Frame: ID: 0x%08x DLC: 0x%02x CANFD Flag: 0x%02x" +xlnx_canfd_reset(char *path, uint32_t val) "%s: Resetting controller with value = 0x%08x" diff --git a/hw/net/can/xlnx-versal-canfd.c b/hw/net/can/xlnx-versal-canfd.c new file mode 100644 index 0000000..5b8ce0a --- /dev/null +++ b/hw/net/can/xlnx-versal-canfd.c @@ -0,0 +1,2107 @@ +/* + * QEMU model of the Xilinx Versal CANFD device. + * + * This implementation is based on the following datasheet: + * https://docs.xilinx.com/v/u/2.0-English/pg223-canfd + * + * Copyright (c) 2023 Advanced Micro Devices, Inc. + * + * Written-by: Vikram Garhwal <vikram.garhwal@amd.com> + * + * Based on QEMU CANFD Device emulation implemented by Jin Yang, Deniz Eren and + * Pavel Pisa + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include "hw/sysbus.h" +#include "hw/irq.h" +#include "hw/register.h" +#include "qapi/error.h" +#include "qemu/bitops.h" +#include "qemu/log.h" +#include "qemu/cutils.h" +#include "qemu/event_notifier.h" +#include "hw/qdev-properties.h" +#include "qom/object_interfaces.h" +#include "migration/vmstate.h" +#include "hw/net/xlnx-versal-canfd.h" +#include "trace.h" + +REG32(SOFTWARE_RESET_REGISTER, 0x0) + FIELD(SOFTWARE_RESET_REGISTER, CEN, 1, 1) + FIELD(SOFTWARE_RESET_REGISTER, SRST, 0, 1) +REG32(MODE_SELECT_REGISTER, 0x4) + FIELD(MODE_SELECT_REGISTER, ITO, 8, 8) + FIELD(MODE_SELECT_REGISTER, ABR, 7, 1) + FIELD(MODE_SELECT_REGISTER, SBR, 6, 1) + FIELD(MODE_SELECT_REGISTER, DPEE, 5, 1) + FIELD(MODE_SELECT_REGISTER, DAR, 4, 1) + FIELD(MODE_SELECT_REGISTER, BRSD, 3, 1) + FIELD(MODE_SELECT_REGISTER, SNOOP, 2, 1) + FIELD(MODE_SELECT_REGISTER, LBACK, 1, 1) + FIELD(MODE_SELECT_REGISTER, SLEEP, 0, 1) +REG32(ARBITRATION_PHASE_BAUD_RATE_PRESCALER_REGISTER, 0x8) + FIELD(ARBITRATION_PHASE_BAUD_RATE_PRESCALER_REGISTER, BRP, 0, 8) +REG32(ARBITRATION_PHASE_BIT_TIMING_REGISTER, 0xc) + FIELD(ARBITRATION_PHASE_BIT_TIMING_REGISTER, SJW, 16, 7) + FIELD(ARBITRATION_PHASE_BIT_TIMING_REGISTER, TS2, 8, 7) + FIELD(ARBITRATION_PHASE_BIT_TIMING_REGISTER, TS1, 0, 8) +REG32(ERROR_COUNTER_REGISTER, 0x10) + FIELD(ERROR_COUNTER_REGISTER, REC, 8, 8) + FIELD(ERROR_COUNTER_REGISTER, TEC, 0, 8) +REG32(ERROR_STATUS_REGISTER, 0x14) + FIELD(ERROR_STATUS_REGISTER, F_BERR, 11, 1) + FIELD(ERROR_STATUS_REGISTER, F_STER, 10, 1) + FIELD(ERROR_STATUS_REGISTER, F_FMER, 9, 1) + FIELD(ERROR_STATUS_REGISTER, F_CRCER, 8, 1) + FIELD(ERROR_STATUS_REGISTER, ACKER, 4, 1) + FIELD(ERROR_STATUS_REGISTER, BERR, 3, 1) + FIELD(ERROR_STATUS_REGISTER, STER, 2, 1) + FIELD(ERROR_STATUS_REGISTER, FMER, 1, 1) + FIELD(ERROR_STATUS_REGISTER, CRCER, 0, 1) +REG32(STATUS_REGISTER, 0x18) + FIELD(STATUS_REGISTER, TDCV, 16, 7) + FIELD(STATUS_REGISTER, SNOOP, 12, 1) + FIELD(STATUS_REGISTER, BSFR_CONFIG, 10, 1) + FIELD(STATUS_REGISTER, PEE_CONFIG, 9, 1) + FIELD(STATUS_REGISTER, ESTAT, 7, 2) + FIELD(STATUS_REGISTER, ERRWRN, 6, 1) + FIELD(STATUS_REGISTER, BBSY, 5, 1) + FIELD(STATUS_REGISTER, BIDLE, 4, 1) + FIELD(STATUS_REGISTER, NORMAL, 3, 1) + FIELD(STATUS_REGISTER, SLEEP, 2, 1) + FIELD(STATUS_REGISTER, LBACK, 1, 1) + FIELD(STATUS_REGISTER, CONFIG, 0, 1) +REG32(INTERRUPT_STATUS_REGISTER, 0x1c) + FIELD(INTERRUPT_STATUS_REGISTER, TXEWMFLL, 31, 1) + FIELD(INTERRUPT_STATUS_REGISTER, TXEOFLW, 30, 1) + FIELD(INTERRUPT_STATUS_REGISTER, RXBOFLW_BI, 24, 6) + FIELD(INTERRUPT_STATUS_REGISTER, RXLRM_BI, 18, 6) + FIELD(INTERRUPT_STATUS_REGISTER, RXMNF, 17, 1) + FIELD(INTERRUPT_STATUS_REGISTER, RXFWMFLL_1, 16, 1) + FIELD(INTERRUPT_STATUS_REGISTER, RXFOFLW_1, 15, 1) + FIELD(INTERRUPT_STATUS_REGISTER, TXCRS, 14, 1) + FIELD(INTERRUPT_STATUS_REGISTER, TXRRS, 13, 1) + FIELD(INTERRUPT_STATUS_REGISTER, RXFWMFLL, 12, 1) + FIELD(INTERRUPT_STATUS_REGISTER, WKUP, 11, 1) + FIELD(INTERRUPT_STATUS_REGISTER, SLP, 10, 1) + FIELD(INTERRUPT_STATUS_REGISTER, BSOFF, 9, 1) + /* + * In the original HW description below bit is named as ERROR but an ERROR + * field name collides with a macro in Windows build. To avoid Windows build + * failures, the bit is renamed to ERROR_BIT. + */ + FIELD(INTERRUPT_STATUS_REGISTER, ERROR_BIT, 8, 1) + FIELD(INTERRUPT_STATUS_REGISTER, RXFOFLW, 6, 1) + FIELD(INTERRUPT_STATUS_REGISTER, TSCNT_OFLW, 5, 1) + FIELD(INTERRUPT_STATUS_REGISTER, RXOK, 4, 1) + FIELD(INTERRUPT_STATUS_REGISTER, BSFRD, 3, 1) + FIELD(INTERRUPT_STATUS_REGISTER, PEE, 2, 1) + FIELD(INTERRUPT_STATUS_REGISTER, TXOK, 1, 1) + FIELD(INTERRUPT_STATUS_REGISTER, ARBLST, 0, 1) +REG32(INTERRUPT_ENABLE_REGISTER, 0x20) + FIELD(INTERRUPT_ENABLE_REGISTER, ETXEWMFLL, 31, 1) + FIELD(INTERRUPT_ENABLE_REGISTER, ETXEOFLW, 30, 1) + FIELD(INTERRUPT_ENABLE_REGISTER, ERXMNF, 17, 1) + FIELD(INTERRUPT_ENABLE_REGISTER, ERXFWMFLL_1, 16, 1) + FIELD(INTERRUPT_ENABLE_REGISTER, ERXFOFLW_1, 15, 1) + FIELD(INTERRUPT_ENABLE_REGISTER, ETXCRS, 14, 1) + FIELD(INTERRUPT_ENABLE_REGISTER, ETXRRS, 13, 1) + FIELD(INTERRUPT_ENABLE_REGISTER, ERXFWMFLL, 12, 1) + FIELD(INTERRUPT_ENABLE_REGISTER, EWKUP, 11, 1) + FIELD(INTERRUPT_ENABLE_REGISTER, ESLP, 10, 1) + FIELD(INTERRUPT_ENABLE_REGISTER, EBSOFF, 9, 1) + FIELD(INTERRUPT_ENABLE_REGISTER, EERROR, 8, 1) + FIELD(INTERRUPT_ENABLE_REGISTER, ERFXOFLW, 6, 1) + FIELD(INTERRUPT_ENABLE_REGISTER, ETSCNT_OFLW, 5, 1) + FIELD(INTERRUPT_ENABLE_REGISTER, ERXOK, 4, 1) + FIELD(INTERRUPT_ENABLE_REGISTER, EBSFRD, 3, 1) + FIELD(INTERRUPT_ENABLE_REGISTER, EPEE, 2, 1) + FIELD(INTERRUPT_ENABLE_REGISTER, ETXOK, 1, 1) + FIELD(INTERRUPT_ENABLE_REGISTER, EARBLOST, 0, 1) +REG32(INTERRUPT_CLEAR_REGISTER, 0x24) + FIELD(INTERRUPT_CLEAR_REGISTER, CTXEWMFLL, 31, 1) + FIELD(INTERRUPT_CLEAR_REGISTER, CTXEOFLW, 30, 1) + FIELD(INTERRUPT_CLEAR_REGISTER, CRXMNF, 17, 1) + FIELD(INTERRUPT_CLEAR_REGISTER, CRXFWMFLL_1, 16, 1) + FIELD(INTERRUPT_CLEAR_REGISTER, CRXFOFLW_1, 15, 1) + FIELD(INTERRUPT_CLEAR_REGISTER, CTXCRS, 14, 1) + FIELD(INTERRUPT_CLEAR_REGISTER, CTXRRS, 13, 1) + FIELD(INTERRUPT_CLEAR_REGISTER, CRXFWMFLL, 12, 1) + FIELD(INTERRUPT_CLEAR_REGISTER, CWKUP, 11, 1) + FIELD(INTERRUPT_CLEAR_REGISTER, CSLP, 10, 1) + FIELD(INTERRUPT_CLEAR_REGISTER, CBSOFF, 9, 1) + FIELD(INTERRUPT_CLEAR_REGISTER, CERROR, 8, 1) + FIELD(INTERRUPT_CLEAR_REGISTER, CRFXOFLW, 6, 1) + FIELD(INTERRUPT_CLEAR_REGISTER, CTSCNT_OFLW, 5, 1) + FIELD(INTERRUPT_CLEAR_REGISTER, CRXOK, 4, 1) + FIELD(INTERRUPT_CLEAR_REGISTER, CBSFRD, 3, 1) + FIELD(INTERRUPT_CLEAR_REGISTER, CPEE, 2, 1) + FIELD(INTERRUPT_CLEAR_REGISTER, CTXOK, 1, 1) + FIELD(INTERRUPT_CLEAR_REGISTER, CARBLOST, 0, 1) +REG32(TIMESTAMP_REGISTER, 0x28) + FIELD(TIMESTAMP_REGISTER, TIMESTAMP_CNT, 16, 16) + FIELD(TIMESTAMP_REGISTER, CTS, 0, 1) +REG32(DATA_PHASE_BAUD_RATE_PRESCALER_REGISTER, 0x88) + FIELD(DATA_PHASE_BAUD_RATE_PRESCALER_REGISTER, TDC, 16, 1) + FIELD(DATA_PHASE_BAUD_RATE_PRESCALER_REGISTER, TDCOFF, 8, 6) + FIELD(DATA_PHASE_BAUD_RATE_PRESCALER_REGISTER, DP_BRP, 0, 8) +REG32(DATA_PHASE_BIT_TIMING_REGISTER, 0x8c) + FIELD(DATA_PHASE_BIT_TIMING_REGISTER, DP_SJW, 16, 4) + FIELD(DATA_PHASE_BIT_TIMING_REGISTER, DP_TS2, 8, 4) + FIELD(DATA_PHASE_BIT_TIMING_REGISTER, DP_TS1, 0, 5) +REG32(TX_BUFFER_READY_REQUEST_REGISTER, 0x90) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR31, 31, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR30, 30, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR29, 29, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR28, 28, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR27, 27, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR26, 26, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR25, 25, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR24, 24, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR23, 23, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR22, 22, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR21, 21, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR20, 20, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR19, 19, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR18, 18, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR17, 17, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR16, 16, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR15, 15, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR14, 14, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR13, 13, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR12, 12, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR11, 11, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR10, 10, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR9, 9, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR8, 8, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR7, 7, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR6, 6, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR5, 5, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR4, 4, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR3, 3, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR2, 2, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR1, 1, 1) + FIELD(TX_BUFFER_READY_REQUEST_REGISTER, RR0, 0, 1) +REG32(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, 0x94) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS31, 31, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS30, 30, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS29, 29, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS28, 28, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS27, 27, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS26, 26, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS25, 25, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS24, 24, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS23, 23, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS22, 22, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS21, 21, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS20, 20, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS19, 19, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS18, 18, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS17, 17, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS16, 16, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS15, 15, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS14, 14, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS13, 13, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS12, 12, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS11, 11, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS10, 10, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS9, 9, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS8, 8, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS7, 7, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS6, 6, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS5, 5, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS4, 4, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS3, 3, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS2, 2, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS1, 1, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, ERRS0, 0, 1) +REG32(TX_BUFFER_CANCEL_REQUEST_REGISTER, 0x98) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR31, 31, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR30, 30, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR29, 29, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR28, 28, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR27, 27, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR26, 26, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR25, 25, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR24, 24, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR23, 23, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR22, 22, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR21, 21, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR20, 20, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR19, 19, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR18, 18, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR17, 17, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR16, 16, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR15, 15, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR14, 14, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR13, 13, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR12, 12, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR11, 11, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR10, 10, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR9, 9, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR8, 8, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR7, 7, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR6, 6, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR5, 5, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR4, 4, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR3, 3, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR2, 2, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR1, 1, 1) + FIELD(TX_BUFFER_CANCEL_REQUEST_REGISTER, CR0, 0, 1) +REG32(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, 0x9c) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS31, 31, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS30, 30, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS29, 29, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS28, 28, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS27, 27, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS26, 26, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS25, 25, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS24, 24, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS23, 23, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS22, 22, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS21, 21, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS20, 20, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS19, 19, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS18, 18, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS17, 17, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS16, 16, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS15, 15, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS14, 14, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS13, 13, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS12, 12, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS11, 11, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS10, 10, + 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS9, 9, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS8, 8, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS7, 7, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS6, 6, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS5, 5, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS4, 4, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS3, 3, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS2, 2, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS1, 1, 1) + FIELD(INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, ECRS0, 0, 1) +REG32(TX_EVENT_FIFO_STATUS_REGISTER, 0xa0) + FIELD(TX_EVENT_FIFO_STATUS_REGISTER, TXE_FL, 8, 6) + FIELD(TX_EVENT_FIFO_STATUS_REGISTER, TXE_IRI, 7, 1) + FIELD(TX_EVENT_FIFO_STATUS_REGISTER, TXE_RI, 0, 5) +REG32(TX_EVENT_FIFO_WATERMARK_REGISTER, 0xa4) + FIELD(TX_EVENT_FIFO_WATERMARK_REGISTER, TXE_FWM, 0, 5) +REG32(ACCEPTANCE_FILTER_CONTROL_REGISTER, 0xe0) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF31, 31, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF30, 30, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF29, 29, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF28, 28, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF27, 27, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF26, 26, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF25, 25, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF24, 24, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF23, 23, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF22, 22, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF21, 21, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF20, 20, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF19, 19, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF18, 18, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF17, 17, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF16, 16, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF15, 15, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF14, 14, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF13, 13, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF12, 12, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF11, 11, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF10, 10, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF9, 9, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF8, 8, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF7, 7, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF6, 6, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF5, 5, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF4, 4, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF3, 3, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF2, 2, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF1, 1, 1) + FIELD(ACCEPTANCE_FILTER_CONTROL_REGISTER, UAF0, 0, 1) +REG32(RX_FIFO_STATUS_REGISTER, 0xe8) + FIELD(RX_FIFO_STATUS_REGISTER, FL_1, 24, 7) + FIELD(RX_FIFO_STATUS_REGISTER, IRI_1, 23, 1) + FIELD(RX_FIFO_STATUS_REGISTER, RI_1, 16, 6) + FIELD(RX_FIFO_STATUS_REGISTER, FL, 8, 7) + FIELD(RX_FIFO_STATUS_REGISTER, IRI, 7, 1) + FIELD(RX_FIFO_STATUS_REGISTER, RI, 0, 6) +REG32(RX_FIFO_WATERMARK_REGISTER, 0xec) + FIELD(RX_FIFO_WATERMARK_REGISTER, RXFP, 16, 5) + FIELD(RX_FIFO_WATERMARK_REGISTER, RXFWM_1, 8, 6) + FIELD(RX_FIFO_WATERMARK_REGISTER, RXFWM, 0, 6) +REG32(TB_ID_REGISTER, 0x100) + FIELD(TB_ID_REGISTER, ID, 21, 11) + FIELD(TB_ID_REGISTER, SRR_RTR_RRS, 20, 1) + FIELD(TB_ID_REGISTER, IDE, 19, 1) + FIELD(TB_ID_REGISTER, ID_EXT, 1, 18) + FIELD(TB_ID_REGISTER, RTR_RRS, 0, 1) +REG32(TB0_DLC_REGISTER, 0x104) + FIELD(TB0_DLC_REGISTER, DLC, 28, 4) + FIELD(TB0_DLC_REGISTER, FDF, 27, 1) + FIELD(TB0_DLC_REGISTER, BRS, 26, 1) + FIELD(TB0_DLC_REGISTER, RSVD2, 25, 1) + FIELD(TB0_DLC_REGISTER, EFC, 24, 1) + FIELD(TB0_DLC_REGISTER, MM, 16, 8) + FIELD(TB0_DLC_REGISTER, RSVD1, 0, 16) +REG32(TB_DW0_REGISTER, 0x108) + FIELD(TB_DW0_REGISTER, DATA_BYTES0, 24, 8) + FIELD(TB_DW0_REGISTER, DATA_BYTES1, 16, 8) + FIELD(TB_DW0_REGISTER, DATA_BYTES2, 8, 8) + FIELD(TB_DW0_REGISTER, DATA_BYTES3, 0, 8) +REG32(TB_DW1_REGISTER, 0x10c) + FIELD(TB_DW1_REGISTER, DATA_BYTES4, 24, 8) + FIELD(TB_DW1_REGISTER, DATA_BYTES5, 16, 8) + FIELD(TB_DW1_REGISTER, DATA_BYTES6, 8, 8) + FIELD(TB_DW1_REGISTER, DATA_BYTES7, 0, 8) +REG32(TB_DW2_REGISTER, 0x110) + FIELD(TB_DW2_REGISTER, DATA_BYTES8, 24, 8) + FIELD(TB_DW2_REGISTER, DATA_BYTES9, 16, 8) + FIELD(TB_DW2_REGISTER, DATA_BYTES10, 8, 8) + FIELD(TB_DW2_REGISTER, DATA_BYTES11, 0, 8) +REG32(TB_DW3_REGISTER, 0x114) + FIELD(TB_DW3_REGISTER, DATA_BYTES12, 24, 8) + FIELD(TB_DW3_REGISTER, DATA_BYTES13, 16, 8) + FIELD(TB_DW3_REGISTER, DATA_BYTES14, 8, 8) + FIELD(TB_DW3_REGISTER, DATA_BYTES15, 0, 8) +REG32(TB_DW4_REGISTER, 0x118) + FIELD(TB_DW4_REGISTER, DATA_BYTES16, 24, 8) + FIELD(TB_DW4_REGISTER, DATA_BYTES17, 16, 8) + FIELD(TB_DW4_REGISTER, DATA_BYTES18, 8, 8) + FIELD(TB_DW4_REGISTER, DATA_BYTES19, 0, 8) +REG32(TB_DW5_REGISTER, 0x11c) + FIELD(TB_DW5_REGISTER, DATA_BYTES20, 24, 8) + FIELD(TB_DW5_REGISTER, DATA_BYTES21, 16, 8) + FIELD(TB_DW5_REGISTER, DATA_BYTES22, 8, 8) + FIELD(TB_DW5_REGISTER, DATA_BYTES23, 0, 8) +REG32(TB_DW6_REGISTER, 0x120) + FIELD(TB_DW6_REGISTER, DATA_BYTES24, 24, 8) + FIELD(TB_DW6_REGISTER, DATA_BYTES25, 16, 8) + FIELD(TB_DW6_REGISTER, DATA_BYTES26, 8, 8) + FIELD(TB_DW6_REGISTER, DATA_BYTES27, 0, 8) +REG32(TB_DW7_REGISTER, 0x124) + FIELD(TB_DW7_REGISTER, DATA_BYTES28, 24, 8) + FIELD(TB_DW7_REGISTER, DATA_BYTES29, 16, 8) + FIELD(TB_DW7_REGISTER, DATA_BYTES30, 8, 8) + FIELD(TB_DW7_REGISTER, DATA_BYTES31, 0, 8) +REG32(TB_DW8_REGISTER, 0x128) + FIELD(TB_DW8_REGISTER, DATA_BYTES32, 24, 8) + FIELD(TB_DW8_REGISTER, DATA_BYTES33, 16, 8) + FIELD(TB_DW8_REGISTER, DATA_BYTES34, 8, 8) + FIELD(TB_DW8_REGISTER, DATA_BYTES35, 0, 8) +REG32(TB_DW9_REGISTER, 0x12c) + FIELD(TB_DW9_REGISTER, DATA_BYTES36, 24, 8) + FIELD(TB_DW9_REGISTER, DATA_BYTES37, 16, 8) + FIELD(TB_DW9_REGISTER, DATA_BYTES38, 8, 8) + FIELD(TB_DW9_REGISTER, DATA_BYTES39, 0, 8) +REG32(TB_DW10_REGISTER, 0x130) + FIELD(TB_DW10_REGISTER, DATA_BYTES40, 24, 8) + FIELD(TB_DW10_REGISTER, DATA_BYTES41, 16, 8) + FIELD(TB_DW10_REGISTER, DATA_BYTES42, 8, 8) + FIELD(TB_DW10_REGISTER, DATA_BYTES43, 0, 8) +REG32(TB_DW11_REGISTER, 0x134) + FIELD(TB_DW11_REGISTER, DATA_BYTES44, 24, 8) + FIELD(TB_DW11_REGISTER, DATA_BYTES45, 16, 8) + FIELD(TB_DW11_REGISTER, DATA_BYTES46, 8, 8) + FIELD(TB_DW11_REGISTER, DATA_BYTES47, 0, 8) +REG32(TB_DW12_REGISTER, 0x138) + FIELD(TB_DW12_REGISTER, DATA_BYTES48, 24, 8) + FIELD(TB_DW12_REGISTER, DATA_BYTES49, 16, 8) + FIELD(TB_DW12_REGISTER, DATA_BYTES50, 8, 8) + FIELD(TB_DW12_REGISTER, DATA_BYTES51, 0, 8) +REG32(TB_DW13_REGISTER, 0x13c) + FIELD(TB_DW13_REGISTER, DATA_BYTES52, 24, 8) + FIELD(TB_DW13_REGISTER, DATA_BYTES53, 16, 8) + FIELD(TB_DW13_REGISTER, DATA_BYTES54, 8, 8) + FIELD(TB_DW13_REGISTER, DATA_BYTES55, 0, 8) +REG32(TB_DW14_REGISTER, 0x140) + FIELD(TB_DW14_REGISTER, DATA_BYTES56, 24, 8) + FIELD(TB_DW14_REGISTER, DATA_BYTES57, 16, 8) + FIELD(TB_DW14_REGISTER, DATA_BYTES58, 8, 8) + FIELD(TB_DW14_REGISTER, DATA_BYTES59, 0, 8) +REG32(TB_DW15_REGISTER, 0x144) + FIELD(TB_DW15_REGISTER, DATA_BYTES60, 24, 8) + FIELD(TB_DW15_REGISTER, DATA_BYTES61, 16, 8) + FIELD(TB_DW15_REGISTER, DATA_BYTES62, 8, 8) + FIELD(TB_DW15_REGISTER, DATA_BYTES63, 0, 8) +REG32(AFMR_REGISTER, 0xa00) + FIELD(AFMR_REGISTER, AMID, 21, 11) + FIELD(AFMR_REGISTER, AMSRR, 20, 1) + FIELD(AFMR_REGISTER, AMIDE, 19, 1) + FIELD(AFMR_REGISTER, AMID_EXT, 1, 18) + FIELD(AFMR_REGISTER, AMRTR, 0, 1) +REG32(AFIR_REGISTER, 0xa04) + FIELD(AFIR_REGISTER, AIID, 21, 11) + FIELD(AFIR_REGISTER, AISRR, 20, 1) + FIELD(AFIR_REGISTER, AIIDE, 19, 1) + FIELD(AFIR_REGISTER, AIID_EXT, 1, 18) + FIELD(AFIR_REGISTER, AIRTR, 0, 1) +REG32(TXE_FIFO_TB_ID_REGISTER, 0x2000) + FIELD(TXE_FIFO_TB_ID_REGISTER, ID, 21, 11) + FIELD(TXE_FIFO_TB_ID_REGISTER, SRR_RTR_RRS, 20, 1) + FIELD(TXE_FIFO_TB_ID_REGISTER, IDE, 19, 1) + FIELD(TXE_FIFO_TB_ID_REGISTER, ID_EXT, 1, 18) + FIELD(TXE_FIFO_TB_ID_REGISTER, RTR_RRS, 0, 1) +REG32(TXE_FIFO_TB_DLC_REGISTER, 0x2004) + FIELD(TXE_FIFO_TB_DLC_REGISTER, DLC, 28, 4) + FIELD(TXE_FIFO_TB_DLC_REGISTER, FDF, 27, 1) + FIELD(TXE_FIFO_TB_DLC_REGISTER, BRS, 26, 1) + FIELD(TXE_FIFO_TB_DLC_REGISTER, ET, 24, 2) + FIELD(TXE_FIFO_TB_DLC_REGISTER, MM, 16, 8) + FIELD(TXE_FIFO_TB_DLC_REGISTER, TIMESTAMP, 0, 16) +REG32(RB_ID_REGISTER, 0x2100) + FIELD(RB_ID_REGISTER, ID, 21, 11) + FIELD(RB_ID_REGISTER, SRR_RTR_RRS, 20, 1) + FIELD(RB_ID_REGISTER, IDE, 19, 1) + FIELD(RB_ID_REGISTER, ID_EXT, 1, 18) + FIELD(RB_ID_REGISTER, RTR_RRS, 0, 1) +REG32(RB_DLC_REGISTER, 0x2104) + FIELD(RB_DLC_REGISTER, DLC, 28, 4) + FIELD(RB_DLC_REGISTER, FDF, 27, 1) + FIELD(RB_DLC_REGISTER, BRS, 26, 1) + FIELD(RB_DLC_REGISTER, ESI, 25, 1) + FIELD(RB_DLC_REGISTER, MATCHED_FILTER_INDEX, 16, 5) + FIELD(RB_DLC_REGISTER, TIMESTAMP, 0, 16) +REG32(RB_DW0_REGISTER, 0x2108) + FIELD(RB_DW0_REGISTER, DATA_BYTES0, 24, 8) + FIELD(RB_DW0_REGISTER, DATA_BYTES1, 16, 8) + FIELD(RB_DW0_REGISTER, DATA_BYTES2, 8, 8) + FIELD(RB_DW0_REGISTER, DATA_BYTES3, 0, 8) +REG32(RB_DW1_REGISTER, 0x210c) + FIELD(RB_DW1_REGISTER, DATA_BYTES4, 24, 8) + FIELD(RB_DW1_REGISTER, DATA_BYTES5, 16, 8) + FIELD(RB_DW1_REGISTER, DATA_BYTES6, 8, 8) + FIELD(RB_DW1_REGISTER, DATA_BYTES7, 0, 8) +REG32(RB_DW2_REGISTER, 0x2110) + FIELD(RB_DW2_REGISTER, DATA_BYTES8, 24, 8) + FIELD(RB_DW2_REGISTER, DATA_BYTES9, 16, 8) + FIELD(RB_DW2_REGISTER, DATA_BYTES10, 8, 8) + FIELD(RB_DW2_REGISTER, DATA_BYTES11, 0, 8) +REG32(RB_DW3_REGISTER, 0x2114) + FIELD(RB_DW3_REGISTER, DATA_BYTES12, 24, 8) + FIELD(RB_DW3_REGISTER, DATA_BYTES13, 16, 8) + FIELD(RB_DW3_REGISTER, DATA_BYTES14, 8, 8) + FIELD(RB_DW3_REGISTER, DATA_BYTES15, 0, 8) +REG32(RB_DW4_REGISTER, 0x2118) + FIELD(RB_DW4_REGISTER, DATA_BYTES16, 24, 8) + FIELD(RB_DW4_REGISTER, DATA_BYTES17, 16, 8) + FIELD(RB_DW4_REGISTER, DATA_BYTES18, 8, 8) + FIELD(RB_DW4_REGISTER, DATA_BYTES19, 0, 8) +REG32(RB_DW5_REGISTER, 0x211c) + FIELD(RB_DW5_REGISTER, DATA_BYTES20, 24, 8) + FIELD(RB_DW5_REGISTER, DATA_BYTES21, 16, 8) + FIELD(RB_DW5_REGISTER, DATA_BYTES22, 8, 8) + FIELD(RB_DW5_REGISTER, DATA_BYTES23, 0, 8) +REG32(RB_DW6_REGISTER, 0x2120) + FIELD(RB_DW6_REGISTER, DATA_BYTES24, 24, 8) + FIELD(RB_DW6_REGISTER, DATA_BYTES25, 16, 8) + FIELD(RB_DW6_REGISTER, DATA_BYTES26, 8, 8) + FIELD(RB_DW6_REGISTER, DATA_BYTES27, 0, 8) +REG32(RB_DW7_REGISTER, 0x2124) + FIELD(RB_DW7_REGISTER, DATA_BYTES28, 24, 8) + FIELD(RB_DW7_REGISTER, DATA_BYTES29, 16, 8) + FIELD(RB_DW7_REGISTER, DATA_BYTES30, 8, 8) + FIELD(RB_DW7_REGISTER, DATA_BYTES31, 0, 8) +REG32(RB_DW8_REGISTER, 0x2128) + FIELD(RB_DW8_REGISTER, DATA_BYTES32, 24, 8) + FIELD(RB_DW8_REGISTER, DATA_BYTES33, 16, 8) + FIELD(RB_DW8_REGISTER, DATA_BYTES34, 8, 8) + FIELD(RB_DW8_REGISTER, DATA_BYTES35, 0, 8) +REG32(RB_DW9_REGISTER, 0x212c) + FIELD(RB_DW9_REGISTER, DATA_BYTES36, 24, 8) + FIELD(RB_DW9_REGISTER, DATA_BYTES37, 16, 8) + FIELD(RB_DW9_REGISTER, DATA_BYTES38, 8, 8) + FIELD(RB_DW9_REGISTER, DATA_BYTES39, 0, 8) +REG32(RB_DW10_REGISTER, 0x2130) + FIELD(RB_DW10_REGISTER, DATA_BYTES40, 24, 8) + FIELD(RB_DW10_REGISTER, DATA_BYTES41, 16, 8) + FIELD(RB_DW10_REGISTER, DATA_BYTES42, 8, 8) + FIELD(RB_DW10_REGISTER, DATA_BYTES43, 0, 8) +REG32(RB_DW11_REGISTER, 0x2134) + FIELD(RB_DW11_REGISTER, DATA_BYTES44, 24, 8) + FIELD(RB_DW11_REGISTER, DATA_BYTES45, 16, 8) + FIELD(RB_DW11_REGISTER, DATA_BYTES46, 8, 8) + FIELD(RB_DW11_REGISTER, DATA_BYTES47, 0, 8) +REG32(RB_DW12_REGISTER, 0x2138) + FIELD(RB_DW12_REGISTER, DATA_BYTES48, 24, 8) + FIELD(RB_DW12_REGISTER, DATA_BYTES49, 16, 8) + FIELD(RB_DW12_REGISTER, DATA_BYTES50, 8, 8) + FIELD(RB_DW12_REGISTER, DATA_BYTES51, 0, 8) +REG32(RB_DW13_REGISTER, 0x213c) + FIELD(RB_DW13_REGISTER, DATA_BYTES52, 24, 8) + FIELD(RB_DW13_REGISTER, DATA_BYTES53, 16, 8) + FIELD(RB_DW13_REGISTER, DATA_BYTES54, 8, 8) + FIELD(RB_DW13_REGISTER, DATA_BYTES55, 0, 8) +REG32(RB_DW14_REGISTER, 0x2140) + FIELD(RB_DW14_REGISTER, DATA_BYTES56, 24, 8) + FIELD(RB_DW14_REGISTER, DATA_BYTES57, 16, 8) + FIELD(RB_DW14_REGISTER, DATA_BYTES58, 8, 8) + FIELD(RB_DW14_REGISTER, DATA_BYTES59, 0, 8) +REG32(RB_DW15_REGISTER, 0x2144) + FIELD(RB_DW15_REGISTER, DATA_BYTES60, 24, 8) + FIELD(RB_DW15_REGISTER, DATA_BYTES61, 16, 8) + FIELD(RB_DW15_REGISTER, DATA_BYTES62, 8, 8) + FIELD(RB_DW15_REGISTER, DATA_BYTES63, 0, 8) +REG32(RB_ID_REGISTER_1, 0x4100) + FIELD(RB_ID_REGISTER_1, ID, 21, 11) + FIELD(RB_ID_REGISTER_1, SRR_RTR_RRS, 20, 1) + FIELD(RB_ID_REGISTER_1, IDE, 19, 1) + FIELD(RB_ID_REGISTER_1, ID_EXT, 1, 18) + FIELD(RB_ID_REGISTER_1, RTR_RRS, 0, 1) +REG32(RB_DLC_REGISTER_1, 0x4104) + FIELD(RB_DLC_REGISTER_1, DLC, 28, 4) + FIELD(RB_DLC_REGISTER_1, FDF, 27, 1) + FIELD(RB_DLC_REGISTER_1, BRS, 26, 1) + FIELD(RB_DLC_REGISTER_1, ESI, 25, 1) + FIELD(RB_DLC_REGISTER_1, MATCHED_FILTER_INDEX, 16, 5) + FIELD(RB_DLC_REGISTER_1, TIMESTAMP, 0, 16) +REG32(RB0_DW0_REGISTER_1, 0x4108) + FIELD(RB0_DW0_REGISTER_1, DATA_BYTES0, 24, 8) + FIELD(RB0_DW0_REGISTER_1, DATA_BYTES1, 16, 8) + FIELD(RB0_DW0_REGISTER_1, DATA_BYTES2, 8, 8) + FIELD(RB0_DW0_REGISTER_1, DATA_BYTES3, 0, 8) +REG32(RB_DW1_REGISTER_1, 0x410c) + FIELD(RB_DW1_REGISTER_1, DATA_BYTES4, 24, 8) + FIELD(RB_DW1_REGISTER_1, DATA_BYTES5, 16, 8) + FIELD(RB_DW1_REGISTER_1, DATA_BYTES6, 8, 8) + FIELD(RB_DW1_REGISTER_1, DATA_BYTES7, 0, 8) +REG32(RB_DW2_REGISTER_1, 0x4110) + FIELD(RB_DW2_REGISTER_1, DATA_BYTES8, 24, 8) + FIELD(RB_DW2_REGISTER_1, DATA_BYTES9, 16, 8) + FIELD(RB_DW2_REGISTER_1, DATA_BYTES10, 8, 8) + FIELD(RB_DW2_REGISTER_1, DATA_BYTES11, 0, 8) +REG32(RB_DW3_REGISTER_1, 0x4114) + FIELD(RB_DW3_REGISTER_1, DATA_BYTES12, 24, 8) + FIELD(RB_DW3_REGISTER_1, DATA_BYTES13, 16, 8) + FIELD(RB_DW3_REGISTER_1, DATA_BYTES14, 8, 8) + FIELD(RB_DW3_REGISTER_1, DATA_BYTES15, 0, 8) +REG32(RB_DW4_REGISTER_1, 0x4118) + FIELD(RB_DW4_REGISTER_1, DATA_BYTES16, 24, 8) + FIELD(RB_DW4_REGISTER_1, DATA_BYTES17, 16, 8) + FIELD(RB_DW4_REGISTER_1, DATA_BYTES18, 8, 8) + FIELD(RB_DW4_REGISTER_1, DATA_BYTES19, 0, 8) +REG32(RB_DW5_REGISTER_1, 0x411c) + FIELD(RB_DW5_REGISTER_1, DATA_BYTES20, 24, 8) + FIELD(RB_DW5_REGISTER_1, DATA_BYTES21, 16, 8) + FIELD(RB_DW5_REGISTER_1, DATA_BYTES22, 8, 8) + FIELD(RB_DW5_REGISTER_1, DATA_BYTES23, 0, 8) +REG32(RB_DW6_REGISTER_1, 0x4120) + FIELD(RB_DW6_REGISTER_1, DATA_BYTES24, 24, 8) + FIELD(RB_DW6_REGISTER_1, DATA_BYTES25, 16, 8) + FIELD(RB_DW6_REGISTER_1, DATA_BYTES26, 8, 8) + FIELD(RB_DW6_REGISTER_1, DATA_BYTES27, 0, 8) +REG32(RB_DW7_REGISTER_1, 0x4124) + FIELD(RB_DW7_REGISTER_1, DATA_BYTES28, 24, 8) + FIELD(RB_DW7_REGISTER_1, DATA_BYTES29, 16, 8) + FIELD(RB_DW7_REGISTER_1, DATA_BYTES30, 8, 8) + FIELD(RB_DW7_REGISTER_1, DATA_BYTES31, 0, 8) +REG32(RB_DW8_REGISTER_1, 0x4128) + FIELD(RB_DW8_REGISTER_1, DATA_BYTES32, 24, 8) + FIELD(RB_DW8_REGISTER_1, DATA_BYTES33, 16, 8) + FIELD(RB_DW8_REGISTER_1, DATA_BYTES34, 8, 8) + FIELD(RB_DW8_REGISTER_1, DATA_BYTES35, 0, 8) +REG32(RB_DW9_REGISTER_1, 0x412c) + FIELD(RB_DW9_REGISTER_1, DATA_BYTES36, 24, 8) + FIELD(RB_DW9_REGISTER_1, DATA_BYTES37, 16, 8) + FIELD(RB_DW9_REGISTER_1, DATA_BYTES38, 8, 8) + FIELD(RB_DW9_REGISTER_1, DATA_BYTES39, 0, 8) +REG32(RB_DW10_REGISTER_1, 0x4130) + FIELD(RB_DW10_REGISTER_1, DATA_BYTES40, 24, 8) + FIELD(RB_DW10_REGISTER_1, DATA_BYTES41, 16, 8) + FIELD(RB_DW10_REGISTER_1, DATA_BYTES42, 8, 8) + FIELD(RB_DW10_REGISTER_1, DATA_BYTES43, 0, 8) +REG32(RB_DW11_REGISTER_1, 0x4134) + FIELD(RB_DW11_REGISTER_1, DATA_BYTES44, 24, 8) + FIELD(RB_DW11_REGISTER_1, DATA_BYTES45, 16, 8) + FIELD(RB_DW11_REGISTER_1, DATA_BYTES46, 8, 8) + FIELD(RB_DW11_REGISTER_1, DATA_BYTES47, 0, 8) +REG32(RB_DW12_REGISTER_1, 0x4138) + FIELD(RB_DW12_REGISTER_1, DATA_BYTES48, 24, 8) + FIELD(RB_DW12_REGISTER_1, DATA_BYTES49, 16, 8) + FIELD(RB_DW12_REGISTER_1, DATA_BYTES50, 8, 8) + FIELD(RB_DW12_REGISTER_1, DATA_BYTES51, 0, 8) +REG32(RB_DW13_REGISTER_1, 0x413c) + FIELD(RB_DW13_REGISTER_1, DATA_BYTES52, 24, 8) + FIELD(RB_DW13_REGISTER_1, DATA_BYTES53, 16, 8) + FIELD(RB_DW13_REGISTER_1, DATA_BYTES54, 8, 8) + FIELD(RB_DW13_REGISTER_1, DATA_BYTES55, 0, 8) +REG32(RB_DW14_REGISTER_1, 0x4140) + FIELD(RB_DW14_REGISTER_1, DATA_BYTES56, 24, 8) + FIELD(RB_DW14_REGISTER_1, DATA_BYTES57, 16, 8) + FIELD(RB_DW14_REGISTER_1, DATA_BYTES58, 8, 8) + FIELD(RB_DW14_REGISTER_1, DATA_BYTES59, 0, 8) +REG32(RB_DW15_REGISTER_1, 0x4144) + FIELD(RB_DW15_REGISTER_1, DATA_BYTES60, 24, 8) + FIELD(RB_DW15_REGISTER_1, DATA_BYTES61, 16, 8) + FIELD(RB_DW15_REGISTER_1, DATA_BYTES62, 8, 8) + FIELD(RB_DW15_REGISTER_1, DATA_BYTES63, 0, 8) + +static uint8_t canfd_dlc_array[8] = {8, 12, 16, 20, 24, 32, 48, 64}; + +static void canfd_update_irq(XlnxVersalCANFDState *s) +{ + unsigned int irq = s->regs[R_INTERRUPT_STATUS_REGISTER] & + s->regs[R_INTERRUPT_ENABLE_REGISTER]; + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + + /* RX watermark interrupts. */ + if (ARRAY_FIELD_EX32(s->regs, RX_FIFO_STATUS_REGISTER, FL) > + ARRAY_FIELD_EX32(s->regs, RX_FIFO_WATERMARK_REGISTER, RXFWM)) { + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXFWMFLL, 1); + } + + if (ARRAY_FIELD_EX32(s->regs, RX_FIFO_STATUS_REGISTER, FL_1) > + ARRAY_FIELD_EX32(s->regs, RX_FIFO_WATERMARK_REGISTER, RXFWM_1)) { + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXFWMFLL_1, 1); + } + + /* TX watermark interrupt. */ + if (ARRAY_FIELD_EX32(s->regs, TX_EVENT_FIFO_STATUS_REGISTER, TXE_FL) > + ARRAY_FIELD_EX32(s->regs, TX_EVENT_FIFO_WATERMARK_REGISTER, TXE_FWM)) { + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, TXEWMFLL, 1); + } + + trace_xlnx_canfd_update_irq(path, s->regs[R_INTERRUPT_STATUS_REGISTER], + s->regs[R_INTERRUPT_ENABLE_REGISTER], irq); + + qemu_set_irq(s->irq_canfd_int, irq); +} + +static void canfd_ier_post_write(RegisterInfo *reg, uint64_t val64) +{ + XlnxVersalCANFDState *s = XILINX_CANFD(reg->opaque); + + canfd_update_irq(s); +} + +static uint64_t canfd_icr_pre_write(RegisterInfo *reg, uint64_t val64) +{ + XlnxVersalCANFDState *s = XILINX_CANFD(reg->opaque); + uint32_t val = val64; + + s->regs[R_INTERRUPT_STATUS_REGISTER] &= ~val; + + /* + * RXBOFLW_BI field is automatically cleared to default if RXBOFLW bit is + * cleared in ISR. + */ + if (ARRAY_FIELD_EX32(s->regs, INTERRUPT_STATUS_REGISTER, RXFWMFLL_1)) { + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXBOFLW_BI, 0); + } + + canfd_update_irq(s); + + return 0; +} + +static void canfd_config_reset(XlnxVersalCANFDState *s) +{ + + unsigned int i; + + /* Reset all the configuration registers. */ + for (i = 0; i < R_RX_FIFO_WATERMARK_REGISTER; ++i) { + register_reset(&s->reg_info[i]); + } + + canfd_update_irq(s); +} + +static void canfd_config_mode(XlnxVersalCANFDState *s) +{ + register_reset(&s->reg_info[R_ERROR_COUNTER_REGISTER]); + register_reset(&s->reg_info[R_ERROR_STATUS_REGISTER]); + register_reset(&s->reg_info[R_STATUS_REGISTER]); + + /* Put XlnxVersalCANFDState in configuration mode. */ + ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, CONFIG, 1); + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, WKUP, 0); + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, SLP, 0); + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, BSOFF, 0); + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, ERROR_BIT, 0); + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXFOFLW, 0); + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXFOFLW_1, 0); + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXOK, 0); + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, TXOK, 0); + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, ARBLST, 0); + + /* Clear the time stamp. */ + ptimer_transaction_begin(s->canfd_timer); + ptimer_set_count(s->canfd_timer, 0); + ptimer_transaction_commit(s->canfd_timer); + + canfd_update_irq(s); +} + +static void update_status_register_mode_bits(XlnxVersalCANFDState *s) +{ + bool sleep_status = ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, SLEEP); + bool sleep_mode = ARRAY_FIELD_EX32(s->regs, MODE_SELECT_REGISTER, SLEEP); + /* Wake up interrupt bit. */ + bool wakeup_irq_val = !sleep_mode && sleep_status; + /* Sleep interrupt bit. */ + bool sleep_irq_val = sleep_mode && !sleep_status; + + /* Clear previous core mode status bits. */ + ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, LBACK, 0); + ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, SLEEP, 0); + ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, SNOOP, 0); + ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, NORMAL, 0); + + /* set current mode bit and generate irqs accordingly. */ + if (ARRAY_FIELD_EX32(s->regs, MODE_SELECT_REGISTER, LBACK)) { + ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, LBACK, 1); + } else if (ARRAY_FIELD_EX32(s->regs, MODE_SELECT_REGISTER, SLEEP)) { + ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, SLEEP, 1); + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, SLP, + sleep_irq_val); + } else if (ARRAY_FIELD_EX32(s->regs, MODE_SELECT_REGISTER, SNOOP)) { + ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, SNOOP, 1); + } else { + /* If all bits are zero, XlnxVersalCANFDState is set in normal mode. */ + ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, NORMAL, 1); + /* Set wakeup interrupt bit. */ + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, WKUP, + wakeup_irq_val); + } + + /* Put the CANFD in error active state. */ + ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, ESTAT, 1); + + canfd_update_irq(s); +} + +static uint64_t canfd_msr_pre_write(RegisterInfo *reg, uint64_t val64) +{ + XlnxVersalCANFDState *s = XILINX_CANFD(reg->opaque); + uint32_t val = val64; + uint8_t multi_mode = 0; + + /* + * Multiple mode set check. This is done to make sure user doesn't set + * multiple modes. + */ + multi_mode = FIELD_EX32(val, MODE_SELECT_REGISTER, LBACK) + + FIELD_EX32(val, MODE_SELECT_REGISTER, SLEEP) + + FIELD_EX32(val, MODE_SELECT_REGISTER, SNOOP); + + if (multi_mode > 1) { + qemu_log_mask(LOG_GUEST_ERROR, "Attempting to configure several modes" + " simultaneously. One mode will be selected according to" + " their priority: LBACK > SLEEP > SNOOP.\n"); + } + + if (ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, CEN) == 0) { + /* In configuration mode, any mode can be selected. */ + s->regs[R_MODE_SELECT_REGISTER] = val; + } else { + bool sleep_mode_bit = FIELD_EX32(val, MODE_SELECT_REGISTER, SLEEP); + + ARRAY_FIELD_DP32(s->regs, MODE_SELECT_REGISTER, SLEEP, sleep_mode_bit); + + if (FIELD_EX32(val, MODE_SELECT_REGISTER, LBACK)) { + qemu_log_mask(LOG_GUEST_ERROR, "Attempting to set LBACK mode" + " without setting CEN bit as 0\n"); + } else if (FIELD_EX32(val, MODE_SELECT_REGISTER, SNOOP)) { + qemu_log_mask(LOG_GUEST_ERROR, "Attempting to set SNOOP mode" + " without setting CEN bit as 0\n"); + } + + update_status_register_mode_bits(s); + } + + return s->regs[R_MODE_SELECT_REGISTER]; +} + +static void canfd_exit_sleep_mode(XlnxVersalCANFDState *s) +{ + ARRAY_FIELD_DP32(s->regs, MODE_SELECT_REGISTER, SLEEP, 0); + update_status_register_mode_bits(s); +} + +static void regs2frame(XlnxVersalCANFDState *s, qemu_can_frame *frame, + uint32_t reg_num) +{ + uint32_t i = 0; + uint32_t j = 0; + uint32_t val = 0; + uint32_t dlc_reg_val = 0; + uint32_t dlc_value = 0; + + /* Check that reg_num should be within TX register space. */ + assert(reg_num <= R_TB_ID_REGISTER + (NUM_REGS_PER_MSG_SPACE * + s->cfg.tx_fifo)); + + dlc_reg_val = s->regs[reg_num + 1]; + dlc_value = FIELD_EX32(dlc_reg_val, TB0_DLC_REGISTER, DLC); + + frame->can_id = s->regs[reg_num]; + + if (FIELD_EX32(dlc_reg_val, TB0_DLC_REGISTER, FDF)) { + /* + * CANFD frame. + * Converting dlc(0 to 15) 4 Byte data to plain length(i.e. 0 to 64) + * 1 Byte data. This is done to make it work with SocketCAN. + * On actual CANFD frame, this value can't be more than 0xF. + * Conversion table for DLC to plain length: + * + * DLC Plain Length + * 0 - 8 0 - 8 + * 9 9 - 12 + * 10 13 - 16 + * 11 17 - 20 + * 12 21 - 24 + * 13 25 - 32 + * 14 33 - 48 + * 15 49 - 64 + */ + + frame->flags = QEMU_CAN_FRMF_TYPE_FD; + + if (dlc_value < 8) { + frame->can_dlc = dlc_value; + } else { + assert((dlc_value - 8) < ARRAY_SIZE(canfd_dlc_array)); + frame->can_dlc = canfd_dlc_array[dlc_value - 8]; + } + } else { + /* + * FD Format bit not set that means it is a CAN Frame. + * Conversion table for classic CAN: + * + * DLC Plain Length + * 0 - 7 0 - 7 + * 8 - 15 8 + */ + + if (dlc_value > 8) { + frame->can_dlc = 8; + qemu_log_mask(LOG_GUEST_ERROR, "Maximum DLC value for Classic CAN" + " frame is 8. Only 8 byte data will be sent.\n"); + } else { + frame->can_dlc = dlc_value; + } + } + + for (j = 0; j < frame->can_dlc; j++) { + val = 8 * i; + + frame->data[j] = extract32(s->regs[reg_num + 2 + (j / 4)], val, 8); + i++; + + if (i % 4 == 0) { + i = 0; + } + } +} + +static void process_cancellation_requests(XlnxVersalCANFDState *s) +{ + uint32_t clear_mask = s->regs[R_TX_BUFFER_READY_REQUEST_REGISTER] & + s->regs[R_TX_BUFFER_CANCEL_REQUEST_REGISTER]; + + s->regs[R_TX_BUFFER_READY_REQUEST_REGISTER] &= ~clear_mask; + s->regs[R_TX_BUFFER_CANCEL_REQUEST_REGISTER] &= ~clear_mask; + + canfd_update_irq(s); +} + +static void store_rx_sequential(XlnxVersalCANFDState *s, + const qemu_can_frame *frame, + uint32_t fill_level, uint32_t read_index, + uint32_t store_location, uint8_t rx_fifo, + bool rx_fifo_id, uint8_t filter_index) +{ + int i; + bool is_canfd_frame; + uint8_t dlc = frame->can_dlc; + uint8_t rx_reg_num = 0; + uint32_t dlc_reg_val = 0; + uint32_t data_reg_val = 0; + + /* Getting RX0/1 fill level */ + if ((fill_level) > rx_fifo - 1) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + + qemu_log_mask(LOG_GUEST_ERROR, "%s: RX%d Buffer is full. Discarding the" + " message\n", path, rx_fifo_id); + + /* Set the corresponding RF buffer overflow interrupt. */ + if (rx_fifo_id == 0) { + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXFOFLW, 1); + } else { + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXFOFLW_1, 1); + } + } else { + uint16_t rx_timestamp = CANFD_TIMER_MAX - + ptimer_get_count(s->canfd_timer); + + if (rx_timestamp == 0xFFFF) { + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, TSCNT_OFLW, 1); + } else { + ARRAY_FIELD_DP32(s->regs, TIMESTAMP_REGISTER, TIMESTAMP_CNT, + rx_timestamp); + } + + if (rx_fifo_id == 0) { + ARRAY_FIELD_DP32(s->regs, RX_FIFO_STATUS_REGISTER, FL, + fill_level + 1); + assert(store_location <= + R_RB_ID_REGISTER + (s->cfg.rx0_fifo * + NUM_REGS_PER_MSG_SPACE)); + } else { + ARRAY_FIELD_DP32(s->regs, RX_FIFO_STATUS_REGISTER, FL_1, + fill_level + 1); + assert(store_location <= + R_RB_ID_REGISTER_1 + (s->cfg.rx1_fifo * + NUM_REGS_PER_MSG_SPACE)); + } + + s->regs[store_location] = frame->can_id; + + dlc = frame->can_dlc; + + if (frame->flags == QEMU_CAN_FRMF_TYPE_FD) { + is_canfd_frame = true; + + /* Store dlc value in Xilinx specific format. */ + for (i = 0; i < ARRAY_SIZE(canfd_dlc_array); i++) { + if (canfd_dlc_array[i] == frame->can_dlc) { + dlc_reg_val = FIELD_DP32(0, RB_DLC_REGISTER, DLC, 8 + i); + } + } + } else { + is_canfd_frame = false; + + if (frame->can_dlc > 8) { + dlc = 8; + } + + dlc_reg_val = FIELD_DP32(0, RB_DLC_REGISTER, DLC, dlc); + } + + dlc_reg_val |= FIELD_DP32(0, RB_DLC_REGISTER, FDF, is_canfd_frame); + dlc_reg_val |= FIELD_DP32(0, RB_DLC_REGISTER, TIMESTAMP, rx_timestamp); + dlc_reg_val |= FIELD_DP32(0, RB_DLC_REGISTER, MATCHED_FILTER_INDEX, + filter_index); + s->regs[store_location + 1] = dlc_reg_val; + + for (i = 0; i < dlc; i++) { + /* Register size is 4 byte but frame->data each is 1 byte. */ + switch (i % 4) { + case 0: + rx_reg_num = i / 4; + + data_reg_val = FIELD_DP32(0, RB_DW0_REGISTER, DATA_BYTES3, + frame->data[i]); + break; + case 1: + data_reg_val |= FIELD_DP32(0, RB_DW0_REGISTER, DATA_BYTES2, + frame->data[i]); + break; + case 2: + data_reg_val |= FIELD_DP32(0, RB_DW0_REGISTER, DATA_BYTES1, + frame->data[i]); + break; + case 3: + data_reg_val |= FIELD_DP32(0, RB_DW0_REGISTER, DATA_BYTES0, + frame->data[i]); + /* + * Last Bytes data which means we have all 4 bytes ready to + * store in one rx regs. + */ + s->regs[store_location + rx_reg_num + 2] = data_reg_val; + break; + } + } + + if (i % 4) { + /* + * In case DLC is not multiplier of 4, data is not saved to RX FIFO + * in above switch case. Store the remaining bytes here. + */ + s->regs[store_location + rx_reg_num + 2] = data_reg_val; + } + + /* set the interrupt as RXOK. */ + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXOK, 1); + } +} + +static void update_rx_sequential(XlnxVersalCANFDState *s, + const qemu_can_frame *frame) +{ + bool filter_pass = false; + uint8_t filter_index = 0; + int i; + int filter_partition = ARRAY_FIELD_EX32(s->regs, + RX_FIFO_WATERMARK_REGISTER, RXFP); + uint32_t store_location; + uint32_t fill_level; + uint32_t read_index; + uint8_t store_index = 0; + g_autofree char *path = NULL; + /* + * If all UAF bits are set to 0, then received messages are not stored + * in the RX buffers. + */ + if (s->regs[R_ACCEPTANCE_FILTER_CONTROL_REGISTER]) { + uint32_t acceptance_filter_status = + s->regs[R_ACCEPTANCE_FILTER_CONTROL_REGISTER]; + + for (i = 0; i < 32; i++) { + if (acceptance_filter_status & 0x1) { + uint32_t msg_id_masked = s->regs[R_AFMR_REGISTER + 2 * i] & + frame->can_id; + uint32_t afir_id_masked = s->regs[R_AFIR_REGISTER + 2 * i] & + s->regs[R_AFMR_REGISTER + 2 * i]; + uint16_t std_msg_id_masked = FIELD_EX32(msg_id_masked, + AFIR_REGISTER, AIID); + uint16_t std_afir_id_masked = FIELD_EX32(afir_id_masked, + AFIR_REGISTER, AIID); + uint32_t ext_msg_id_masked = FIELD_EX32(msg_id_masked, + AFIR_REGISTER, + AIID_EXT); + uint32_t ext_afir_id_masked = FIELD_EX32(afir_id_masked, + AFIR_REGISTER, + AIID_EXT); + bool ext_ide = FIELD_EX32(s->regs[R_AFMR_REGISTER + 2 * i], + AFMR_REGISTER, AMIDE); + + if (std_msg_id_masked == std_afir_id_masked) { + if (ext_ide) { + /* Extended message ID message. */ + if (ext_msg_id_masked == ext_afir_id_masked) { + filter_pass = true; + filter_index = i; + + break; + } + } else { + /* Standard message ID. */ + filter_pass = true; + filter_index = i; + + break; + } + } + } + acceptance_filter_status >>= 1; + } + } + + if (!filter_pass) { + path = object_get_canonical_path(OBJECT(s)); + + trace_xlnx_canfd_rx_fifo_filter_reject(path, frame->can_id, + frame->can_dlc); + } else { + if (filter_index <= filter_partition) { + fill_level = ARRAY_FIELD_EX32(s->regs, RX_FIFO_STATUS_REGISTER, FL); + read_index = ARRAY_FIELD_EX32(s->regs, RX_FIFO_STATUS_REGISTER, RI); + store_index = read_index + fill_level; + + if (read_index == s->cfg.rx0_fifo - 1) { + /* + * When ri is s->cfg.rx0_fifo - 1 i.e. max, it goes cyclic that + * means we reset the ri to 0x0. + */ + read_index = 0; + ARRAY_FIELD_DP32(s->regs, RX_FIFO_STATUS_REGISTER, RI, + read_index); + } + + if (store_index > s->cfg.rx0_fifo - 1) { + store_index -= s->cfg.rx0_fifo - 1; + } + + store_location = R_RB_ID_REGISTER + + (store_index * NUM_REGS_PER_MSG_SPACE); + + store_rx_sequential(s, frame, fill_level, read_index, + store_location, s->cfg.rx0_fifo, 0, + filter_index); + } else { + /* RX 1 fill level message */ + fill_level = ARRAY_FIELD_EX32(s->regs, RX_FIFO_STATUS_REGISTER, + FL_1); + read_index = ARRAY_FIELD_EX32(s->regs, RX_FIFO_STATUS_REGISTER, + RI_1); + store_index = read_index + fill_level; + + if (read_index == s->cfg.rx1_fifo - 1) { + /* + * When ri is s->cfg.rx1_fifo - 1 i.e. max, it goes cyclic that + * means we reset the ri to 0x0. + */ + read_index = 0; + ARRAY_FIELD_DP32(s->regs, RX_FIFO_STATUS_REGISTER, RI_1, + read_index); + } + + if (store_index > s->cfg.rx1_fifo - 1) { + store_index -= s->cfg.rx1_fifo - 1; + } + + store_location = R_RB_ID_REGISTER_1 + + (store_index * NUM_REGS_PER_MSG_SPACE); + + store_rx_sequential(s, frame, fill_level, read_index, + store_location, s->cfg.rx1_fifo, 1, + filter_index); + } + + path = object_get_canonical_path(OBJECT(s)); + + trace_xlnx_canfd_rx_data(path, frame->can_id, frame->can_dlc, + frame->flags); + canfd_update_irq(s); + } +} + +static bool tx_ready_check(XlnxVersalCANFDState *s) +{ + if (ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, SRST)) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + + qemu_log_mask(LOG_GUEST_ERROR, "%s: Attempting to transfer data while" + " XlnxVersalCANFDState is in reset mode\n", path); + + return false; + } + + if (ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, CEN) == 0) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + + qemu_log_mask(LOG_GUEST_ERROR, "%s: Attempting to transfer data while" + " XlnxVersalCANFDState is in configuration mode." + " Reset the core so operations can start fresh\n", + path); + return false; + } + + if (ARRAY_FIELD_EX32(s->regs, MODE_SELECT_REGISTER, SNOOP)) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + + qemu_log_mask(LOG_GUEST_ERROR, "%s: Attempting to transfer data while" + " XlnxVersalCANFDState is in SNOOP MODE\n", + path); + return false; + } + + return true; +} + +static void tx_fifo_stamp(XlnxVersalCANFDState *s, uint32_t tb0_regid) +{ + /* + * If EFC bit in DLC message is set, this means we will store the + * event of this transmitted message with time stamp. + */ + uint32_t dlc_reg_val = 0; + + if (FIELD_EX32(s->regs[tb0_regid + 1], TB0_DLC_REGISTER, EFC)) { + uint8_t dlc_val = FIELD_EX32(s->regs[tb0_regid + 1], TB0_DLC_REGISTER, + DLC); + bool fdf_val = FIELD_EX32(s->regs[tb0_regid + 1], TB0_DLC_REGISTER, + FDF); + bool brs_val = FIELD_EX32(s->regs[tb0_regid + 1], TB0_DLC_REGISTER, + BRS); + uint8_t mm_val = FIELD_EX32(s->regs[tb0_regid + 1], TB0_DLC_REGISTER, + MM); + uint8_t fill_level = ARRAY_FIELD_EX32(s->regs, + TX_EVENT_FIFO_STATUS_REGISTER, + TXE_FL); + uint8_t read_index = ARRAY_FIELD_EX32(s->regs, + TX_EVENT_FIFO_STATUS_REGISTER, + TXE_RI); + uint8_t store_index = fill_level + read_index; + + if ((fill_level) > s->cfg.tx_fifo - 1) { + qemu_log_mask(LOG_GUEST_ERROR, "TX Event Buffer is full." + " Discarding the message\n"); + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, TXEOFLW, 1); + } else { + if (read_index == s->cfg.tx_fifo - 1) { + /* + * When ri is s->cfg.tx_fifo - 1 i.e. max, it goes cyclic that + * means we reset the ri to 0x0. + */ + read_index = 0; + ARRAY_FIELD_DP32(s->regs, TX_EVENT_FIFO_STATUS_REGISTER, TXE_RI, + read_index); + } + + if (store_index > s->cfg.tx_fifo - 1) { + store_index -= s->cfg.tx_fifo - 1; + } + + assert(store_index < s->cfg.tx_fifo); + + uint32_t tx_event_reg0_id = R_TXE_FIFO_TB_ID_REGISTER + + (store_index * 2); + + /* Store message ID in TX event register. */ + s->regs[tx_event_reg0_id] = s->regs[tb0_regid]; + + uint16_t tx_timestamp = CANFD_TIMER_MAX - + ptimer_get_count(s->canfd_timer); + + /* Store DLC with time stamp in DLC regs. */ + dlc_reg_val = FIELD_DP32(0, TXE_FIFO_TB_DLC_REGISTER, DLC, dlc_val); + dlc_reg_val |= FIELD_DP32(0, TXE_FIFO_TB_DLC_REGISTER, FDF, + fdf_val); + dlc_reg_val |= FIELD_DP32(0, TXE_FIFO_TB_DLC_REGISTER, BRS, + brs_val); + dlc_reg_val |= FIELD_DP32(0, TXE_FIFO_TB_DLC_REGISTER, ET, 0x3); + dlc_reg_val |= FIELD_DP32(0, TXE_FIFO_TB_DLC_REGISTER, MM, mm_val); + dlc_reg_val |= FIELD_DP32(0, TXE_FIFO_TB_DLC_REGISTER, TIMESTAMP, + tx_timestamp); + s->regs[tx_event_reg0_id + 1] = dlc_reg_val; + + ARRAY_FIELD_DP32(s->regs, TX_EVENT_FIFO_STATUS_REGISTER, TXE_FL, + fill_level + 1); + } + } +} + +static gint g_cmp_ids(gconstpointer data1, gconstpointer data2) +{ + tx_ready_reg_info *tx_reg_1 = (tx_ready_reg_info *) data1; + tx_ready_reg_info *tx_reg_2 = (tx_ready_reg_info *) data2; + + return tx_reg_1->can_id - tx_reg_2->can_id; +} + +static void free_list(GSList *list) +{ + GSList *iterator = NULL; + + for (iterator = list; iterator != NULL; iterator = iterator->next) { + g_free((tx_ready_reg_info *)iterator->data); + } + + g_slist_free(list); + + return; +} + +static GSList *prepare_tx_data(XlnxVersalCANFDState *s) +{ + uint8_t i = 0; + GSList *list = NULL; + uint32_t reg_num = 0; + uint32_t reg_ready = s->regs[R_TX_BUFFER_READY_REQUEST_REGISTER]; + + /* First find the messages which are ready for transmission. */ + for (i = 0; i < s->cfg.tx_fifo; i++) { + if (reg_ready & 1) { + reg_num = R_TB_ID_REGISTER + (NUM_REGS_PER_MSG_SPACE * i); + tx_ready_reg_info *temp = g_new(tx_ready_reg_info, 1); + + temp->can_id = s->regs[reg_num]; + temp->reg_num = reg_num; + list = g_slist_prepend(list, temp); + list = g_slist_sort(list, g_cmp_ids); + } + + reg_ready >>= 1; + } + + s->regs[R_TX_BUFFER_READY_REQUEST_REGISTER] = 0; + s->regs[R_TX_BUFFER_CANCEL_REQUEST_REGISTER] = 0; + + return list; +} + +static void transfer_data(XlnxVersalCANFDState *s) +{ + bool canfd_tx = tx_ready_check(s); + GSList *list, *iterator = NULL; + qemu_can_frame frame; + + if (!canfd_tx) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + + qemu_log_mask(LOG_GUEST_ERROR, "%s: Controller not enabled for data" + " transfer\n", path); + return; + } + + list = prepare_tx_data(s); + if (list == NULL) { + return; + } + + for (iterator = list; iterator != NULL; iterator = iterator->next) { + regs2frame(s, &frame, + ((tx_ready_reg_info *)iterator->data)->reg_num); + + if (ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, LBACK)) { + update_rx_sequential(s, &frame); + tx_fifo_stamp(s, ((tx_ready_reg_info *)iterator->data)->reg_num); + + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, RXOK, 1); + } else { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + + trace_xlnx_canfd_tx_data(path, frame.can_id, frame.can_dlc, + frame.flags); + can_bus_client_send(&s->bus_client, &frame, 1); + tx_fifo_stamp(s, + ((tx_ready_reg_info *)iterator->data)->reg_num); + + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, TXRRS, 1); + + if (ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, SLEEP)) { + canfd_exit_sleep_mode(s); + } + } + } + + ARRAY_FIELD_DP32(s->regs, INTERRUPT_STATUS_REGISTER, TXOK, 1); + free_list(list); + + canfd_update_irq(s); +} + +static uint64_t canfd_srr_pre_write(RegisterInfo *reg, uint64_t val64) +{ + XlnxVersalCANFDState *s = XILINX_CANFD(reg->opaque); + uint32_t val = val64; + + ARRAY_FIELD_DP32(s->regs, SOFTWARE_RESET_REGISTER, CEN, + FIELD_EX32(val, SOFTWARE_RESET_REGISTER, CEN)); + + if (FIELD_EX32(val, SOFTWARE_RESET_REGISTER, SRST)) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + + trace_xlnx_canfd_reset(path, val64); + + /* First, core will do software reset then will enter in config mode. */ + canfd_config_reset(s); + } else if (ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, CEN) == 0) { + canfd_config_mode(s); + } else { + /* + * Leave config mode. Now XlnxVersalCANFD core will enter Normal, Sleep, + * snoop or Loopback mode depending upon LBACK, SLEEP, SNOOP register + * states. + */ + ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, CONFIG, 0); + + ptimer_transaction_begin(s->canfd_timer); + ptimer_set_count(s->canfd_timer, 0); + ptimer_transaction_commit(s->canfd_timer); + update_status_register_mode_bits(s); + transfer_data(s); + } + + return s->regs[R_SOFTWARE_RESET_REGISTER]; +} + +static uint64_t filter_mask(RegisterInfo *reg, uint64_t val64) +{ + XlnxVersalCANFDState *s = XILINX_CANFD(reg->opaque); + uint32_t reg_idx = (reg->access->addr) / 4; + uint32_t val = val64; + uint32_t filter_offset = (reg_idx - R_AFMR_REGISTER) / 2; + + if (!(s->regs[R_ACCEPTANCE_FILTER_CONTROL_REGISTER] & + (1 << filter_offset))) { + s->regs[reg_idx] = val; + } else { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + + qemu_log_mask(LOG_GUEST_ERROR, "%s: Acceptance filter %d not enabled\n", + path, filter_offset + 1); + } + + return s->regs[reg_idx]; +} + +static uint64_t filter_id(RegisterInfo *reg, uint64_t val64) +{ + XlnxVersalCANFDState *s = XILINX_CANFD(reg->opaque); + hwaddr reg_idx = (reg->access->addr) / 4; + uint32_t val = val64; + uint32_t filter_offset = (reg_idx - R_AFIR_REGISTER) / 2; + + if (!(s->regs[R_ACCEPTANCE_FILTER_CONTROL_REGISTER] & + (1 << filter_offset))) { + s->regs[reg_idx] = val; + } else { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + + qemu_log_mask(LOG_GUEST_ERROR, "%s: Acceptance filter %d not enabled\n", + path, filter_offset + 1); + } + + return s->regs[reg_idx]; +} + +static uint64_t canfd_tx_fifo_status_prew(RegisterInfo *reg, uint64_t val64) +{ + XlnxVersalCANFDState *s = XILINX_CANFD(reg->opaque); + uint32_t val = val64; + uint8_t read_ind = 0; + uint8_t fill_ind = ARRAY_FIELD_EX32(s->regs, TX_EVENT_FIFO_STATUS_REGISTER, + TXE_FL); + + if (FIELD_EX32(val, TX_EVENT_FIFO_STATUS_REGISTER, TXE_IRI) && fill_ind) { + read_ind = ARRAY_FIELD_EX32(s->regs, TX_EVENT_FIFO_STATUS_REGISTER, + TXE_RI) + 1; + + if (read_ind > s->cfg.tx_fifo - 1) { + read_ind = 0; + } + + /* + * Increase the read index by 1 and decrease the fill level by 1. + */ + ARRAY_FIELD_DP32(s->regs, TX_EVENT_FIFO_STATUS_REGISTER, TXE_RI, + read_ind); + ARRAY_FIELD_DP32(s->regs, TX_EVENT_FIFO_STATUS_REGISTER, TXE_FL, + fill_ind - 1); + } + + return s->regs[R_TX_EVENT_FIFO_STATUS_REGISTER]; +} + +static uint64_t canfd_rx_fifo_status_prew(RegisterInfo *reg, uint64_t val64) +{ + XlnxVersalCANFDState *s = XILINX_CANFD(reg->opaque); + uint32_t val = val64; + uint8_t read_ind = 0; + uint8_t fill_ind = 0; + + if (FIELD_EX32(val, RX_FIFO_STATUS_REGISTER, IRI)) { + /* FL index is zero, setting IRI bit has no effect. */ + if (FIELD_EX32(val, RX_FIFO_STATUS_REGISTER, FL) != 0) { + read_ind = FIELD_EX32(val, RX_FIFO_STATUS_REGISTER, RI) + 1; + + if (read_ind > s->cfg.rx0_fifo - 1) { + read_ind = 0; + } + + fill_ind = FIELD_EX32(val, RX_FIFO_STATUS_REGISTER, FL) - 1; + + ARRAY_FIELD_DP32(s->regs, RX_FIFO_STATUS_REGISTER, RI, read_ind); + ARRAY_FIELD_DP32(s->regs, RX_FIFO_STATUS_REGISTER, FL, fill_ind); + } + } + + if (FIELD_EX32(val, RX_FIFO_STATUS_REGISTER, IRI_1)) { + /* FL_1 index is zero, setting IRI_1 bit has no effect. */ + if (FIELD_EX32(val, RX_FIFO_STATUS_REGISTER, FL_1) != 0) { + read_ind = FIELD_EX32(val, RX_FIFO_STATUS_REGISTER, RI_1) + 1; + + if (read_ind > s->cfg.rx1_fifo - 1) { + read_ind = 0; + } + + fill_ind = FIELD_EX32(val, RX_FIFO_STATUS_REGISTER, FL_1) - 1; + + ARRAY_FIELD_DP32(s->regs, RX_FIFO_STATUS_REGISTER, RI_1, read_ind); + ARRAY_FIELD_DP32(s->regs, RX_FIFO_STATUS_REGISTER, FL_1, fill_ind); + } + } + + return s->regs[R_RX_FIFO_STATUS_REGISTER]; +} + +static uint64_t canfd_tsr_pre_write(RegisterInfo *reg, uint64_t val64) +{ + XlnxVersalCANFDState *s = XILINX_CANFD(reg->opaque); + uint32_t val = val64; + + if (FIELD_EX32(val, TIMESTAMP_REGISTER, CTS)) { + ARRAY_FIELD_DP32(s->regs, TIMESTAMP_REGISTER, TIMESTAMP_CNT, 0); + ptimer_transaction_begin(s->canfd_timer); + ptimer_set_count(s->canfd_timer, 0); + ptimer_transaction_commit(s->canfd_timer); + } + + return 0; +} + +static uint64_t canfd_trr_reg_prew(RegisterInfo *reg, uint64_t val64) +{ + XlnxVersalCANFDState *s = XILINX_CANFD(reg->opaque); + + if (ARRAY_FIELD_EX32(s->regs, MODE_SELECT_REGISTER, SNOOP)) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + + qemu_log_mask(LOG_GUEST_ERROR, "%s: Controller is in SNOOP mode." + " tx_ready_register will stay in reset mode\n", path); + return 0; + } else { + return val64; + } +} + +static void canfd_trr_reg_postw(RegisterInfo *reg, uint64_t val64) +{ + XlnxVersalCANFDState *s = XILINX_CANFD(reg->opaque); + + transfer_data(s); +} + +static void canfd_cancel_reg_postw(RegisterInfo *reg, uint64_t val64) +{ + XlnxVersalCANFDState *s = XILINX_CANFD(reg->opaque); + + process_cancellation_requests(s); +} + +static uint64_t canfd_write_check_prew(RegisterInfo *reg, uint64_t val64) +{ + XlnxVersalCANFDState *s = XILINX_CANFD(reg->opaque); + uint32_t val = val64; + + if (ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, CEN) == 0) { + return val; + } + return 0; +} + +static const RegisterAccessInfo canfd_tx_regs[] = { + { .name = "TB_ID_REGISTER", .addr = A_TB_ID_REGISTER, + },{ .name = "TB0_DLC_REGISTER", .addr = A_TB0_DLC_REGISTER, + },{ .name = "TB_DW0_REGISTER", .addr = A_TB_DW0_REGISTER, + },{ .name = "TB_DW1_REGISTER", .addr = A_TB_DW1_REGISTER, + },{ .name = "TB_DW2_REGISTER", .addr = A_TB_DW2_REGISTER, + },{ .name = "TB_DW3_REGISTER", .addr = A_TB_DW3_REGISTER, + },{ .name = "TB_DW4_REGISTER", .addr = A_TB_DW4_REGISTER, + },{ .name = "TB_DW5_REGISTER", .addr = A_TB_DW5_REGISTER, + },{ .name = "TB_DW6_REGISTER", .addr = A_TB_DW6_REGISTER, + },{ .name = "TB_DW7_REGISTER", .addr = A_TB_DW7_REGISTER, + },{ .name = "TB_DW8_REGISTER", .addr = A_TB_DW8_REGISTER, + },{ .name = "TB_DW9_REGISTER", .addr = A_TB_DW9_REGISTER, + },{ .name = "TB_DW10_REGISTER", .addr = A_TB_DW10_REGISTER, + },{ .name = "TB_DW11_REGISTER", .addr = A_TB_DW11_REGISTER, + },{ .name = "TB_DW12_REGISTER", .addr = A_TB_DW12_REGISTER, + },{ .name = "TB_DW13_REGISTER", .addr = A_TB_DW13_REGISTER, + },{ .name = "TB_DW14_REGISTER", .addr = A_TB_DW14_REGISTER, + },{ .name = "TB_DW15_REGISTER", .addr = A_TB_DW15_REGISTER, + } +}; + +static const RegisterAccessInfo canfd_rx0_regs[] = { + { .name = "RB_ID_REGISTER", .addr = A_RB_ID_REGISTER, + .ro = 0xffffffff, + },{ .name = "RB_DLC_REGISTER", .addr = A_RB_DLC_REGISTER, + .ro = 0xfe1fffff, + },{ .name = "RB_DW0_REGISTER", .addr = A_RB_DW0_REGISTER, + .ro = 0xffffffff, + },{ .name = "RB_DW1_REGISTER", .addr = A_RB_DW1_REGISTER, + .ro = 0xffffffff, + },{ .name = "RB_DW2_REGISTER", .addr = A_RB_DW2_REGISTER, + .ro = 0xffffffff, + },{ .name = "RB_DW3_REGISTER", .addr = A_RB_DW3_REGISTER, + .ro = 0xffffffff, + },{ .name = "RB_DW4_REGISTER", .addr = A_RB_DW4_REGISTER, + .ro = 0xffffffff, + },{ .name = "RB_DW5_REGISTER", .addr = A_RB_DW5_REGISTER, + .ro = 0xffffffff, + },{ .name = "RB_DW6_REGISTER", .addr = A_RB_DW6_REGISTER, + .ro = 0xffffffff, + },{ .name = "RB_DW7_REGISTER", .addr = A_RB_DW7_REGISTER, + .ro = 0xffffffff, + },{ .name = "RB_DW8_REGISTER", .addr = A_RB_DW8_REGISTER, + .ro = 0xffffffff, + },{ .name = "RB_DW9_REGISTER", .addr = A_RB_DW9_REGISTER, + .ro = 0xffffffff, + },{ .name = "RB_DW10_REGISTER", .addr = A_RB_DW10_REGISTER, + .ro = 0xffffffff, + },{ .name = "RB_DW11_REGISTER", .addr = A_RB_DW11_REGISTER, + .ro = 0xffffffff, + },{ .name = "RB_DW12_REGISTER", .addr = A_RB_DW12_REGISTER, + .ro = 0xffffffff, + },{ .name = "RB_DW13_REGISTER", .addr = A_RB_DW13_REGISTER, + .ro = 0xffffffff, + },{ .name = "RB_DW14_REGISTER", .addr = A_RB_DW14_REGISTER, + .ro = 0xffffffff, + },{ .name = "RB_DW15_REGISTER", .addr = A_RB_DW15_REGISTER, + .ro = 0xffffffff, + } +}; + +static const RegisterAccessInfo canfd_rx1_regs[] = { + { .name = "RB_ID_REGISTER_1", .addr = A_RB_ID_REGISTER_1, + .ro = 0xffffffff, + },{ .name = "RB_DLC_REGISTER_1", .addr = A_RB_DLC_REGISTER_1, + .ro = 0xfe1fffff, + },{ .name = "RB0_DW0_REGISTER_1", .addr = A_RB0_DW0_REGISTER_1, + .ro = 0xffffffff, + },{ .name = "RB_DW1_REGISTER_1", .addr = A_RB_DW1_REGISTER_1, + .ro = 0xffffffff, + },{ .name = "RB_DW2_REGISTER_1", .addr = A_RB_DW2_REGISTER_1, + .ro = 0xffffffff, + },{ .name = "RB_DW3_REGISTER_1", .addr = A_RB_DW3_REGISTER_1, + .ro = 0xffffffff, + },{ .name = "RB_DW4_REGISTER_1", .addr = A_RB_DW4_REGISTER_1, + .ro = 0xffffffff, + },{ .name = "RB_DW5_REGISTER_1", .addr = A_RB_DW5_REGISTER_1, + .ro = 0xffffffff, + },{ .name = "RB_DW6_REGISTER_1", .addr = A_RB_DW6_REGISTER_1, + .ro = 0xffffffff, + },{ .name = "RB_DW7_REGISTER_1", .addr = A_RB_DW7_REGISTER_1, + .ro = 0xffffffff, + },{ .name = "RB_DW8_REGISTER_1", .addr = A_RB_DW8_REGISTER_1, + .ro = 0xffffffff, + },{ .name = "RB_DW9_REGISTER_1", .addr = A_RB_DW9_REGISTER_1, + .ro = 0xffffffff, + },{ .name = "RB_DW10_REGISTER_1", .addr = A_RB_DW10_REGISTER_1, + .ro = 0xffffffff, + },{ .name = "RB_DW11_REGISTER_1", .addr = A_RB_DW11_REGISTER_1, + .ro = 0xffffffff, + },{ .name = "RB_DW12_REGISTER_1", .addr = A_RB_DW12_REGISTER_1, + .ro = 0xffffffff, + },{ .name = "RB_DW13_REGISTER_1", .addr = A_RB_DW13_REGISTER_1, + .ro = 0xffffffff, + },{ .name = "RB_DW14_REGISTER_1", .addr = A_RB_DW14_REGISTER_1, + .ro = 0xffffffff, + },{ .name = "RB_DW15_REGISTER_1", .addr = A_RB_DW15_REGISTER_1, + .ro = 0xffffffff, + } +}; + +/* Acceptance filter registers. */ +static const RegisterAccessInfo canfd_af_regs[] = { + { .name = "AFMR_REGISTER", .addr = A_AFMR_REGISTER, + .pre_write = filter_mask, + },{ .name = "AFIR_REGISTER", .addr = A_AFIR_REGISTER, + .pre_write = filter_id, + } +}; + +static const RegisterAccessInfo canfd_txe_regs[] = { + { .name = "TXE_FIFO_TB_ID_REGISTER", .addr = A_TXE_FIFO_TB_ID_REGISTER, + .ro = 0xffffffff, + },{ .name = "TXE_FIFO_TB_DLC_REGISTER", .addr = A_TXE_FIFO_TB_DLC_REGISTER, + .ro = 0xffffffff, + } +}; + +static const RegisterAccessInfo canfd_regs_info[] = { + { .name = "SOFTWARE_RESET_REGISTER", .addr = A_SOFTWARE_RESET_REGISTER, + .pre_write = canfd_srr_pre_write, + },{ .name = "MODE_SELECT_REGISTER", .addr = A_MODE_SELECT_REGISTER, + .pre_write = canfd_msr_pre_write, + },{ .name = "ARBITRATION_PHASE_BAUD_RATE_PRESCALER_REGISTER", + .addr = A_ARBITRATION_PHASE_BAUD_RATE_PRESCALER_REGISTER, + .pre_write = canfd_write_check_prew, + },{ .name = "ARBITRATION_PHASE_BIT_TIMING_REGISTER", + .addr = A_ARBITRATION_PHASE_BIT_TIMING_REGISTER, + .pre_write = canfd_write_check_prew, + },{ .name = "ERROR_COUNTER_REGISTER", .addr = A_ERROR_COUNTER_REGISTER, + .ro = 0xffff, + },{ .name = "ERROR_STATUS_REGISTER", .addr = A_ERROR_STATUS_REGISTER, + .w1c = 0xf1f, + },{ .name = "STATUS_REGISTER", .addr = A_STATUS_REGISTER, + .reset = 0x1, + .ro = 0x7f17ff, + },{ .name = "INTERRUPT_STATUS_REGISTER", + .addr = A_INTERRUPT_STATUS_REGISTER, + .ro = 0xffffff7f, + },{ .name = "INTERRUPT_ENABLE_REGISTER", + .addr = A_INTERRUPT_ENABLE_REGISTER, + .post_write = canfd_ier_post_write, + },{ .name = "INTERRUPT_CLEAR_REGISTER", + .addr = A_INTERRUPT_CLEAR_REGISTER, .pre_write = canfd_icr_pre_write, + },{ .name = "TIMESTAMP_REGISTER", .addr = A_TIMESTAMP_REGISTER, + .ro = 0xffff0000, + .pre_write = canfd_tsr_pre_write, + },{ .name = "DATA_PHASE_BAUD_RATE_PRESCALER_REGISTER", + .addr = A_DATA_PHASE_BAUD_RATE_PRESCALER_REGISTER, + .pre_write = canfd_write_check_prew, + },{ .name = "DATA_PHASE_BIT_TIMING_REGISTER", + .addr = A_DATA_PHASE_BIT_TIMING_REGISTER, + .pre_write = canfd_write_check_prew, + },{ .name = "TX_BUFFER_READY_REQUEST_REGISTER", + .addr = A_TX_BUFFER_READY_REQUEST_REGISTER, + .pre_write = canfd_trr_reg_prew, + .post_write = canfd_trr_reg_postw, + },{ .name = "INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER", + .addr = A_INTERRUPT_ENABLE_TX_BUFFER_READY_REQUEST_REGISTER, + },{ .name = "TX_BUFFER_CANCEL_REQUEST_REGISTER", + .addr = A_TX_BUFFER_CANCEL_REQUEST_REGISTER, + .post_write = canfd_cancel_reg_postw, + },{ .name = "INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER", + .addr = A_INTERRUPT_ENABLE_TX_BUFFER_CANCELLATION_REQUEST_REGISTER, + },{ .name = "TX_EVENT_FIFO_STATUS_REGISTER", + .addr = A_TX_EVENT_FIFO_STATUS_REGISTER, + .ro = 0x3f1f, .pre_write = canfd_tx_fifo_status_prew, + },{ .name = "TX_EVENT_FIFO_WATERMARK_REGISTER", + .addr = A_TX_EVENT_FIFO_WATERMARK_REGISTER, + .reset = 0xf, + .pre_write = canfd_write_check_prew, + },{ .name = "ACCEPTANCE_FILTER_CONTROL_REGISTER", + .addr = A_ACCEPTANCE_FILTER_CONTROL_REGISTER, + },{ .name = "RX_FIFO_STATUS_REGISTER", .addr = A_RX_FIFO_STATUS_REGISTER, + .ro = 0x7f3f7f3f, .pre_write = canfd_rx_fifo_status_prew, + },{ .name = "RX_FIFO_WATERMARK_REGISTER", + .addr = A_RX_FIFO_WATERMARK_REGISTER, + .reset = 0x1f0f0f, + .pre_write = canfd_write_check_prew, + } +}; + +static void xlnx_versal_canfd_ptimer_cb(void *opaque) +{ + /* No action required on the timer rollover. */ +} + +static const MemoryRegionOps canfd_ops = { + .read = register_read_memory, + .write = register_write_memory, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void canfd_reset(DeviceState *dev) +{ + XlnxVersalCANFDState *s = XILINX_CANFD(dev); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(s->reg_info); ++i) { + register_reset(&s->reg_info[i]); + } + + ptimer_transaction_begin(s->canfd_timer); + ptimer_set_count(s->canfd_timer, 0); + ptimer_transaction_commit(s->canfd_timer); +} + +static bool can_xilinx_canfd_receive(CanBusClientState *client) +{ + XlnxVersalCANFDState *s = container_of(client, XlnxVersalCANFDState, + bus_client); + + bool reset_state = ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, SRST); + bool can_enabled = ARRAY_FIELD_EX32(s->regs, SOFTWARE_RESET_REGISTER, CEN); + + return !reset_state && can_enabled; +} + +static ssize_t canfd_xilinx_receive(CanBusClientState *client, + const qemu_can_frame *buf, + size_t buf_size) +{ + XlnxVersalCANFDState *s = container_of(client, XlnxVersalCANFDState, + bus_client); + const qemu_can_frame *frame = buf; + + assert(buf_size > 0); + + if (ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, LBACK)) { + /* + * XlnxVersalCANFDState will not participate in normal bus communication + * and does not receive any messages transmitted by other CAN nodes. + */ + return 1; + } + + /* Update the status register that we are receiving message. */ + ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, BBSY, 1); + + if (ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, SNOOP)) { + /* Snoop Mode: Just keep the data. no response back. */ + update_rx_sequential(s, frame); + } else { + if ((ARRAY_FIELD_EX32(s->regs, STATUS_REGISTER, SLEEP))) { + /* + * XlnxVersalCANFDState is in sleep mode. Any data on bus will bring + * it to the wake up state. + */ + canfd_exit_sleep_mode(s); + } + + update_rx_sequential(s, frame); + } + + /* Message processing done. Update the status back to !busy */ + ARRAY_FIELD_DP32(s->regs, STATUS_REGISTER, BBSY, 0); + return 1; +} + +static CanBusClientInfo canfd_xilinx_bus_client_info = { + .can_receive = can_xilinx_canfd_receive, + .receive = canfd_xilinx_receive, +}; + +static int xlnx_canfd_connect_to_bus(XlnxVersalCANFDState *s, + CanBusState *bus) +{ + s->bus_client.info = &canfd_xilinx_bus_client_info; + + return can_bus_insert_client(bus, &s->bus_client); +} + +#define NUM_REG_PER_AF ARRAY_SIZE(canfd_af_regs) +#define NUM_AF 32 +#define NUM_REG_PER_TXE ARRAY_SIZE(canfd_txe_regs) +#define NUM_TXE 32 + +static int canfd_populate_regarray(XlnxVersalCANFDState *s, + RegisterInfoArray *r_array, int pos, + const RegisterAccessInfo *rae, + int num_rae) +{ + int i; + + for (i = 0; i < num_rae; i++) { + int index = rae[i].addr / 4; + RegisterInfo *r = &s->reg_info[index]; + + object_initialize(r, sizeof(*r), TYPE_REGISTER); + + *r = (RegisterInfo) { + .data = &s->regs[index], + .data_size = sizeof(uint32_t), + .access = &rae[i], + .opaque = OBJECT(s), + }; + + r_array->r[i + pos] = r; + } + return i + pos; +} + +static void canfd_create_rai(RegisterAccessInfo *rai_array, + const RegisterAccessInfo *canfd_regs, + int template_rai_array_sz, + int num_template_to_copy) +{ + int i; + int reg_num; + + for (reg_num = 0; reg_num < num_template_to_copy; reg_num++) { + int pos = reg_num * template_rai_array_sz; + + memcpy(rai_array + pos, canfd_regs, + template_rai_array_sz * sizeof(RegisterAccessInfo)); + + for (i = 0; i < template_rai_array_sz; i++) { + const char *name = canfd_regs[i].name; + uint64_t addr = canfd_regs[i].addr; + rai_array[i + pos].name = g_strdup_printf("%s%d", name, reg_num); + rai_array[i + pos].addr = addr + pos * 4; + } + } +} + +static RegisterInfoArray *canfd_create_regarray(XlnxVersalCANFDState *s) +{ + const char *device_prefix = object_get_typename(OBJECT(s)); + uint64_t memory_size = XLNX_VERSAL_CANFD_R_MAX * 4; + int num_regs; + int pos = 0; + RegisterInfoArray *r_array; + + num_regs = ARRAY_SIZE(canfd_regs_info) + + s->cfg.tx_fifo * NUM_REGS_PER_MSG_SPACE + + s->cfg.rx0_fifo * NUM_REGS_PER_MSG_SPACE + + NUM_AF * NUM_REG_PER_AF + + NUM_TXE * NUM_REG_PER_TXE; + + s->tx_regs = g_new0(RegisterAccessInfo, + s->cfg.tx_fifo * ARRAY_SIZE(canfd_tx_regs)); + + canfd_create_rai(s->tx_regs, canfd_tx_regs, + ARRAY_SIZE(canfd_tx_regs), s->cfg.tx_fifo); + + s->rx0_regs = g_new0(RegisterAccessInfo, + s->cfg.rx0_fifo * ARRAY_SIZE(canfd_rx0_regs)); + + canfd_create_rai(s->rx0_regs, canfd_rx0_regs, + ARRAY_SIZE(canfd_rx0_regs), s->cfg.rx0_fifo); + + s->af_regs = g_new0(RegisterAccessInfo, + NUM_AF * ARRAY_SIZE(canfd_af_regs)); + + canfd_create_rai(s->af_regs, canfd_af_regs, + ARRAY_SIZE(canfd_af_regs), NUM_AF); + + s->txe_regs = g_new0(RegisterAccessInfo, + NUM_TXE * ARRAY_SIZE(canfd_txe_regs)); + + canfd_create_rai(s->txe_regs, canfd_txe_regs, + ARRAY_SIZE(canfd_txe_regs), NUM_TXE); + + if (s->cfg.enable_rx_fifo1) { + num_regs += s->cfg.rx1_fifo * NUM_REGS_PER_MSG_SPACE; + + s->rx1_regs = g_new0(RegisterAccessInfo, + s->cfg.rx1_fifo * ARRAY_SIZE(canfd_rx1_regs)); + + canfd_create_rai(s->rx1_regs, canfd_rx1_regs, + ARRAY_SIZE(canfd_rx1_regs), s->cfg.rx1_fifo); + } + + r_array = g_new0(RegisterInfoArray, 1); + r_array->r = g_new0(RegisterInfo * , num_regs); + r_array->num_elements = num_regs; + r_array->prefix = device_prefix; + + pos = canfd_populate_regarray(s, r_array, pos, + canfd_regs_info, + ARRAY_SIZE(canfd_regs_info)); + pos = canfd_populate_regarray(s, r_array, pos, + s->tx_regs, s->cfg.tx_fifo * + NUM_REGS_PER_MSG_SPACE); + pos = canfd_populate_regarray(s, r_array, pos, + s->rx0_regs, s->cfg.rx0_fifo * + NUM_REGS_PER_MSG_SPACE); + if (s->cfg.enable_rx_fifo1) { + pos = canfd_populate_regarray(s, r_array, pos, + s->rx1_regs, s->cfg.rx1_fifo * + NUM_REGS_PER_MSG_SPACE); + } + pos = canfd_populate_regarray(s, r_array, pos, + s->af_regs, NUM_AF * NUM_REG_PER_AF); + pos = canfd_populate_regarray(s, r_array, pos, + s->txe_regs, NUM_TXE * NUM_REG_PER_TXE); + + memory_region_init_io(&r_array->mem, OBJECT(s), &canfd_ops, r_array, + device_prefix, memory_size); + return r_array; +} + +static void canfd_realize(DeviceState *dev, Error **errp) +{ + XlnxVersalCANFDState *s = XILINX_CANFD(dev); + RegisterInfoArray *reg_array; + + reg_array = canfd_create_regarray(s); + memory_region_add_subregion(&s->iomem, 0x00, ®_array->mem); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem); + sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq_canfd_int); + + if (s->canfdbus) { + if (xlnx_canfd_connect_to_bus(s, s->canfdbus) < 0) { + g_autofree char *path = object_get_canonical_path(OBJECT(s)); + + error_setg(errp, "%s: xlnx_canfd_connect_to_bus failed", path); + return; + } + + } + + /* Allocate a new timer. */ + s->canfd_timer = ptimer_init(xlnx_versal_canfd_ptimer_cb, s, + PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD | + PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT | + PTIMER_POLICY_NO_IMMEDIATE_RELOAD); + + ptimer_transaction_begin(s->canfd_timer); + + ptimer_set_freq(s->canfd_timer, s->cfg.ext_clk_freq); + ptimer_set_limit(s->canfd_timer, CANFD_TIMER_MAX, 1); + ptimer_run(s->canfd_timer, 0); + ptimer_transaction_commit(s->canfd_timer); +} + +static void canfd_init(Object *obj) +{ + XlnxVersalCANFDState *s = XILINX_CANFD(obj); + + memory_region_init(&s->iomem, obj, TYPE_XILINX_CANFD, + XLNX_VERSAL_CANFD_R_MAX * 4); +} + +static const VMStateDescription vmstate_canfd = { + .name = TYPE_XILINX_CANFD, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, XlnxVersalCANFDState, + XLNX_VERSAL_CANFD_R_MAX), + VMSTATE_PTIMER(canfd_timer, XlnxVersalCANFDState), + VMSTATE_END_OF_LIST(), + } +}; + +static Property canfd_core_properties[] = { + DEFINE_PROP_UINT8("rx-fifo0", XlnxVersalCANFDState, cfg.rx0_fifo, 0x40), + DEFINE_PROP_UINT8("rx-fifo1", XlnxVersalCANFDState, cfg.rx1_fifo, 0x40), + DEFINE_PROP_UINT8("tx-fifo", XlnxVersalCANFDState, cfg.tx_fifo, 0x20), + DEFINE_PROP_BOOL("enable-rx-fifo1", XlnxVersalCANFDState, + cfg.enable_rx_fifo1, true), + DEFINE_PROP_UINT32("ext_clk_freq", XlnxVersalCANFDState, cfg.ext_clk_freq, + CANFD_DEFAULT_CLOCK), + DEFINE_PROP_LINK("canfdbus", XlnxVersalCANFDState, canfdbus, TYPE_CAN_BUS, + CanBusState *), + DEFINE_PROP_END_OF_LIST(), +}; + +static void canfd_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = canfd_reset; + dc->realize = canfd_realize; + device_class_set_props(dc, canfd_core_properties); + dc->vmsd = &vmstate_canfd; +} + +static const TypeInfo canfd_info = { + .name = TYPE_XILINX_CANFD, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(XlnxVersalCANFDState), + .class_init = canfd_class_init, + .instance_init = canfd_init, +}; + +static void canfd_register_types(void) +{ + type_register_static(&canfd_info); +} + +type_init(canfd_register_types) diff --git a/hw/sd/allwinner-sdhost.c b/hw/sd/allwinner-sdhost.c index 92a0f42..286e009 100644 --- a/hw/sd/allwinner-sdhost.c +++ b/hw/sd/allwinner-sdhost.c @@ -77,6 +77,7 @@ enum { REG_SD_DATA1_CRC = 0x12C, /* CRC Data 1 from card/eMMC */ REG_SD_DATA0_CRC = 0x130, /* CRC Data 0 from card/eMMC */ REG_SD_CRC_STA = 0x134, /* CRC status from card/eMMC during write */ + REG_SD_SAMP_DL = 0x144, /* Sample Delay Control (sun50i-a64) */ REG_SD_FIFO = 0x200, /* Read/Write FIFO */ }; @@ -158,6 +159,7 @@ enum { REG_SD_RES_CRC_RST = 0x0, REG_SD_DATA_CRC_RST = 0x0, REG_SD_CRC_STA_RST = 0x0, + REG_SD_SAMPLE_DL_RST = 0x00002000, REG_SD_FIFO_RST = 0x0, }; @@ -459,6 +461,7 @@ static uint64_t allwinner_sdhost_read(void *opaque, hwaddr offset, { AwSdHostState *s = AW_SDHOST(opaque); AwSdHostClass *sc = AW_SDHOST_GET_CLASS(s); + bool out_of_bounds = false; uint32_t res = 0; switch (offset) { @@ -577,13 +580,24 @@ static uint64_t allwinner_sdhost_read(void *opaque, hwaddr offset, case REG_SD_FIFO: /* Read/Write FIFO */ res = allwinner_sdhost_fifo_read(s); break; + case REG_SD_SAMP_DL: /* Sample Delay */ + if (sc->can_calibrate) { + res = s->sample_delay; + } else { + out_of_bounds = true; + } + break; default: - qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset %" - HWADDR_PRIx"\n", __func__, offset); + out_of_bounds = true; res = 0; break; } + if (out_of_bounds) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset %" + HWADDR_PRIx"\n", __func__, offset); + } + trace_allwinner_sdhost_read(offset, res, size); return res; } @@ -602,6 +616,7 @@ static void allwinner_sdhost_write(void *opaque, hwaddr offset, { AwSdHostState *s = AW_SDHOST(opaque); AwSdHostClass *sc = AW_SDHOST_GET_CLASS(s); + bool out_of_bounds = false; trace_allwinner_sdhost_write(offset, value, size); @@ -725,10 +740,21 @@ static void allwinner_sdhost_write(void *opaque, hwaddr offset, case REG_SD_DATA0_CRC: /* CRC Data 0 from card/eMMC */ case REG_SD_CRC_STA: /* CRC status from card/eMMC in write operation */ break; + case REG_SD_SAMP_DL: /* Sample delay control */ + if (sc->can_calibrate) { + s->sample_delay = value; + } else { + out_of_bounds = true; + } + break; default: + out_of_bounds = true; + break; + } + + if (out_of_bounds) { qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset %" HWADDR_PRIx"\n", __func__, offset); - break; } } @@ -777,6 +803,7 @@ static const VMStateDescription vmstate_allwinner_sdhost = { VMSTATE_UINT32(response_crc, AwSdHostState), VMSTATE_UINT32_ARRAY(data_crc, AwSdHostState, 8), VMSTATE_UINT32(status_crc, AwSdHostState), + VMSTATE_UINT32(sample_delay, AwSdHostState), VMSTATE_END_OF_LIST() } }; @@ -815,6 +842,7 @@ static void allwinner_sdhost_realize(DeviceState *dev, Error **errp) static void allwinner_sdhost_reset(DeviceState *dev) { AwSdHostState *s = AW_SDHOST(dev); + AwSdHostClass *sc = AW_SDHOST_GET_CLASS(s); s->global_ctl = REG_SD_GCTL_RST; s->clock_ctl = REG_SD_CKCR_RST; @@ -855,6 +883,10 @@ static void allwinner_sdhost_reset(DeviceState *dev) } s->status_crc = REG_SD_CRC_STA_RST; + + if (sc->can_calibrate) { + s->sample_delay = REG_SD_SAMPLE_DL_RST; + } } static void allwinner_sdhost_bus_class_init(ObjectClass *klass, void *data) @@ -879,6 +911,7 @@ static void allwinner_sdhost_sun4i_class_init(ObjectClass *klass, void *data) AwSdHostClass *sc = AW_SDHOST_CLASS(klass); sc->max_desc_size = 8 * KiB; sc->is_sun4i = true; + sc->can_calibrate = false; } static void allwinner_sdhost_sun5i_class_init(ObjectClass *klass, void *data) @@ -886,6 +919,25 @@ static void allwinner_sdhost_sun5i_class_init(ObjectClass *klass, void *data) AwSdHostClass *sc = AW_SDHOST_CLASS(klass); sc->max_desc_size = 64 * KiB; sc->is_sun4i = false; + sc->can_calibrate = false; +} + +static void allwinner_sdhost_sun50i_a64_class_init(ObjectClass *klass, + void *data) +{ + AwSdHostClass *sc = AW_SDHOST_CLASS(klass); + sc->max_desc_size = 64 * KiB; + sc->is_sun4i = false; + sc->can_calibrate = true; +} + +static void allwinner_sdhost_sun50i_a64_emmc_class_init(ObjectClass *klass, + void *data) +{ + AwSdHostClass *sc = AW_SDHOST_CLASS(klass); + sc->max_desc_size = 8 * KiB; + sc->is_sun4i = false; + sc->can_calibrate = true; } static const TypeInfo allwinner_sdhost_info = { @@ -910,6 +962,18 @@ static const TypeInfo allwinner_sdhost_sun5i_info = { .class_init = allwinner_sdhost_sun5i_class_init, }; +static const TypeInfo allwinner_sdhost_sun50i_a64_info = { + .name = TYPE_AW_SDHOST_SUN50I_A64, + .parent = TYPE_AW_SDHOST, + .class_init = allwinner_sdhost_sun50i_a64_class_init, +}; + +static const TypeInfo allwinner_sdhost_sun50i_a64_emmc_info = { + .name = TYPE_AW_SDHOST_SUN50I_A64_EMMC, + .parent = TYPE_AW_SDHOST, + .class_init = allwinner_sdhost_sun50i_a64_emmc_class_init, +}; + static const TypeInfo allwinner_sdhost_bus_info = { .name = TYPE_AW_SDHOST_BUS, .parent = TYPE_SD_BUS, @@ -922,6 +986,8 @@ static void allwinner_sdhost_register_types(void) type_register_static(&allwinner_sdhost_info); type_register_static(&allwinner_sdhost_sun4i_info); type_register_static(&allwinner_sdhost_sun5i_info); + type_register_static(&allwinner_sdhost_sun50i_a64_info); + type_register_static(&allwinner_sdhost_sun50i_a64_emmc_info); type_register_static(&allwinner_sdhost_bus_info); } |