target-arm queue:
* Fix various bugs that might result in an assert() due to incorrect hflags for M-profile CPUs * Fix Aspeed SMC Controller user-mode select handling * Report correct (with-tag) address in fault address register when TBI is enabled * cubieboard: make sure SOC object isn't leaked * fsl-imx25: Wire up eSDHC controllers * fsl-imx25: Wire up USB controllers * New board model: orangepi-pc (OrangePi PC) * ARM/KVM: if user doesn't select GIC version and the host kernel can only provide GICv3, use that, rather than defaulting to "fail because GICv2 isn't possible" * kvm: Only do KVM_SET_VCPU_EVENTS at the last stage of sync -----BEGIN PGP SIGNATURE----- iQJNBAABCAA3FiEE4aXFk81BneKOgxXPPCUl7RQ2DN4FAl5qZsIZHHBldGVyLm1h eWRlbGxAbGluYXJvLm9yZwAKCRA8JSXtFDYM3krfD/40xprKOtpel6si3edDQsw5 j6LamqJDvaUdtG713OjR6yjvvQiXCw9yCDlfGhBlLhLW1t0aGKrrZoRC4CNMOt0J 34WAcDP0iz3ALEwpfNfr/DWFwiGjamabrRsGcq08Q42G7+UA7FhUEvL25ZApfRFy 8O2gs3bDv30pfa5oJwYJhvSHcNeR9YKueK+WGw16gRkSoNbjUxnSpyiGnxbMaNpg aL+ZQzQ0BOAyeOg/0LUdZ9meAvWwR0NppgK0ujJxq68/6tPz8tv2+pQgllNYSQRO vDr4mj6MlJNW62M5IAKRZ/6zTz34+7UYQ7mTK2VTWt2qtfrANz+EpcDljtc/8EIF lAVd1W099DNdqgFcUGZzoWyRbmjz9B76WTJ43orY5AbMZN5l4XwAGItkE6yQbqKd kqPKP2ICFj/0JhgBoTzo0J/5wV2izZKKnih990IJU390oWoiVRbdWlQDJ2ujQ3AV havWhR/tL399K1UZl8act/J9rifq9J3mbiqpx2XEEiFMu93FDNCPtJioix1Swvpx ERMB9VA6JNAHZ6oAGgNmTHG3nSJtpcin8XxR5YcKWSYiksPjkce1sEwtRbyxBHtq jb/yk5mjyrbYTy3Gmg85/Fh74XnELsnwmADFdezHXUu4EPxth/ssCuXlXs8DIciI sWGFVJpDoWSXEqi4FjvhIQ== =LLqm -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/pmaydell/tags/pull-target-arm-20200312' into staging target-arm queue: * Fix various bugs that might result in an assert() due to incorrect hflags for M-profile CPUs * Fix Aspeed SMC Controller user-mode select handling * Report correct (with-tag) address in fault address register when TBI is enabled * cubieboard: make sure SOC object isn't leaked * fsl-imx25: Wire up eSDHC controllers * fsl-imx25: Wire up USB controllers * New board model: orangepi-pc (OrangePi PC) * ARM/KVM: if user doesn't select GIC version and the host kernel can only provide GICv3, use that, rather than defaulting to "fail because GICv2 isn't possible" * kvm: Only do KVM_SET_VCPU_EVENTS at the last stage of sync # gpg: Signature made Thu 12 Mar 2020 16:43:46 GMT # gpg: using RSA key E1A5C593CD419DE28E8315CF3C2525ED14360CDE # gpg: issuer "peter.maydell@linaro.org" # gpg: Good signature from "Peter Maydell <peter.maydell@linaro.org>" [ultimate] # gpg: aka "Peter Maydell <pmaydell@gmail.com>" [ultimate] # gpg: aka "Peter Maydell <pmaydell@chiark.greenend.org.uk>" [ultimate] # Primary key fingerprint: E1A5 C593 CD41 9DE2 8E83 15CF 3C25 25ED 1436 0CDE * remotes/pmaydell/tags/pull-target-arm-20200312: (36 commits) target/arm: kvm: Inject events at the last stage of sync hw/arm/virt: kvm: allow gicv3 by default if v2 cannot work hw/arm/virt: kvm: Restructure finalize_gic_version() target/arm/kvm: Let kvm_arm_vgic_probe() return a bitmap hw/arm/virt: Introduce finalize_gic_version() hw/arm/virt: Introduce VirtGICType enum type hw/arm/virt: Document 'max' value in gic-version property description docs: add Orange Pi PC document tests/boot_linux_console: Test booting NetBSD via U-Boot on OrangePi PC tests/boot_linux_console: Add a SLOW test booting Ubuntu on OrangePi PC tests/boot_linux_console: Add a SD card test for the OrangePi PC board tests/boot_linux_console: Add initrd test for the Orange Pi PC board tests/boot_linux_console: Add a quick test for the OrangePi PC board hw/arm/allwinner: add RTC device support hw/arm/allwinner-h3: add SDRAM controller device hw/arm/allwinner-h3: add Boot ROM support hw/arm/allwinner-h3: add EMAC ethernet device hw/arm/allwinner: add SD/MMC host controller hw/arm/allwinner: add Security Identifier device hw/arm/allwinner: add CPU Configuration module ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
d4f7d56759
@ -492,6 +492,15 @@ F: hw/*/allwinner*
|
||||
F: include/hw/*/allwinner*
|
||||
F: hw/arm/cubieboard.c
|
||||
|
||||
Allwinner-h3
|
||||
M: Niek Linnenbank <nieklinnenbank@gmail.com>
|
||||
L: qemu-arm@nongnu.org
|
||||
S: Maintained
|
||||
F: hw/*/allwinner-h3*
|
||||
F: include/hw/*/allwinner-h3*
|
||||
F: hw/arm/orangepi.c
|
||||
F: docs/system/orangepi.rst
|
||||
|
||||
ARM PrimeCell and CMSDK devices
|
||||
M: Peter Maydell <peter.maydell@linaro.org>
|
||||
L: qemu-arm@nongnu.org
|
||||
|
@ -175,6 +175,7 @@ trace-events-subdirs += hw/scsi
|
||||
trace-events-subdirs += hw/sd
|
||||
trace-events-subdirs += hw/sparc
|
||||
trace-events-subdirs += hw/sparc64
|
||||
trace-events-subdirs += hw/ssi
|
||||
trace-events-subdirs += hw/timer
|
||||
trace-events-subdirs += hw/tpm
|
||||
trace-events-subdirs += hw/usb
|
||||
|
@ -41,3 +41,4 @@ CONFIG_FSL_IMX25=y
|
||||
CONFIG_FSL_IMX7=y
|
||||
CONFIG_FSL_IMX6UL=y
|
||||
CONFIG_SEMIHOSTING=y
|
||||
CONFIG_ALLWINNER_H3=y
|
||||
|
253
docs/system/arm/orangepi.rst
Normal file
253
docs/system/arm/orangepi.rst
Normal file
@ -0,0 +1,253 @@
|
||||
Orange Pi PC (``orangepi-pc``)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The Xunlong Orange Pi PC is an Allwinner H3 System on Chip
|
||||
based embedded computer with mainline support in both U-Boot
|
||||
and Linux. The board comes with a Quad Core Cortex-A7 @ 1.3GHz,
|
||||
1GiB RAM, 100Mbit ethernet, USB, SD/MMC, USB, HDMI and
|
||||
various other I/O.
|
||||
|
||||
Supported devices
|
||||
"""""""""""""""""
|
||||
|
||||
The Orange Pi PC machine supports the following devices:
|
||||
|
||||
* SMP (Quad Core Cortex-A7)
|
||||
* Generic Interrupt Controller configuration
|
||||
* SRAM mappings
|
||||
* SDRAM controller
|
||||
* Real Time Clock
|
||||
* Timer device (re-used from Allwinner A10)
|
||||
* UART
|
||||
* SD/MMC storage controller
|
||||
* EMAC ethernet
|
||||
* USB 2.0 interfaces
|
||||
* Clock Control Unit
|
||||
* System Control module
|
||||
* Security Identifier device
|
||||
|
||||
Limitations
|
||||
"""""""""""
|
||||
|
||||
Currently, Orange Pi PC does *not* support the following features:
|
||||
|
||||
- Graphical output via HDMI, GPU and/or the Display Engine
|
||||
- Audio output
|
||||
- Hardware Watchdog
|
||||
|
||||
Also see the 'unimplemented' array in the Allwinner H3 SoC module
|
||||
for a complete list of unimplemented I/O devices: ``./hw/arm/allwinner-h3.c``
|
||||
|
||||
Boot options
|
||||
""""""""""""
|
||||
|
||||
The Orange Pi PC machine can start using the standard -kernel functionality
|
||||
for loading a Linux kernel or ELF executable. Additionally, the Orange Pi PC
|
||||
machine can also emulate the BootROM which is present on an actual Allwinner H3
|
||||
based SoC, which loads the bootloader from a SD card, specified via the -sd argument
|
||||
to qemu-system-arm.
|
||||
|
||||
Machine-specific options
|
||||
""""""""""""""""""""""""
|
||||
|
||||
The following machine-specific options are supported:
|
||||
|
||||
- allwinner-rtc.base-year=YYYY
|
||||
|
||||
The Allwinner RTC device is automatically created by the Orange Pi PC machine
|
||||
and uses a default base year value which can be overridden using the 'base-year' property.
|
||||
The base year is the actual represented year when the RTC year value is zero.
|
||||
This option can be used in case the target operating system driver uses a different
|
||||
base year value. The minimum value for the base year is 1900.
|
||||
|
||||
- allwinner-sid.identifier=abcd1122-a000-b000-c000-12345678ffff
|
||||
|
||||
The Security Identifier value can be read by the guest.
|
||||
For example, U-Boot uses it to determine a unique MAC address.
|
||||
|
||||
The above machine-specific options can be specified in qemu-system-arm
|
||||
via the '-global' argument, for example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ qemu-system-arm -M orangepi-pc -sd mycard.img \
|
||||
-global allwinner-rtc.base-year=2000
|
||||
|
||||
Running mainline Linux
|
||||
""""""""""""""""""""""
|
||||
|
||||
Mainline Linux kernels from 4.19 up to latest master are known to work.
|
||||
To build a Linux mainline kernel that can be booted by the Orange Pi PC machine,
|
||||
simply configure the kernel using the sunxi_defconfig configuration:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- make mrproper
|
||||
$ ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- make sunxi_defconfig
|
||||
|
||||
To be able to use USB storage, you need to manually enable the corresponding
|
||||
configuration item. Start the kconfig configuration tool:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- make menuconfig
|
||||
|
||||
Navigate to the following item, enable it and save your configuration:
|
||||
|
||||
Device Drivers > USB support > USB Mass Storage support
|
||||
|
||||
Build the Linux kernel with:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- make
|
||||
|
||||
To boot the newly build linux kernel in QEMU with the Orange Pi PC machine, use:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ qemu-system-arm -M orangepi-pc -nic user -nographic \
|
||||
-kernel /path/to/linux/arch/arm/boot/zImage \
|
||||
-append 'console=ttyS0,115200' \
|
||||
-dtb /path/to/linux/arch/arm/boot/dts/sun8i-h3-orangepi-pc.dtb
|
||||
|
||||
Orange Pi PC images
|
||||
"""""""""""""""""""
|
||||
|
||||
Note that the mainline kernel does not have a root filesystem. You may provide it
|
||||
with an official Orange Pi PC image from the official website:
|
||||
|
||||
http://www.orangepi.org/downloadresources/
|
||||
|
||||
Another possibility is to run an Armbian image for Orange Pi PC which
|
||||
can be downloaded from:
|
||||
|
||||
https://www.armbian.com/orange-pi-pc/
|
||||
|
||||
Alternatively, you can also choose to build you own image with buildroot
|
||||
using the orangepi_pc_defconfig. Also see https://buildroot.org for more information.
|
||||
|
||||
You can choose to attach the selected image either as an SD card or as USB mass storage.
|
||||
For example, to boot using the Orange Pi PC Debian image on SD card, simply add the -sd
|
||||
argument and provide the proper root= kernel parameter:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ qemu-system-arm -M orangepi-pc -nic user -nographic \
|
||||
-kernel /path/to/linux/arch/arm/boot/zImage \
|
||||
-append 'console=ttyS0,115200 root=/dev/mmcblk0p2' \
|
||||
-dtb /path/to/linux/arch/arm/boot/dts/sun8i-h3-orangepi-pc.dtb \
|
||||
-sd OrangePi_pc_debian_stretch_server_linux5.3.5_v1.0.img
|
||||
|
||||
To attach the image as an USB mass storage device to the machine,
|
||||
simply append to the command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
-drive if=none,id=stick,file=myimage.img \
|
||||
-device usb-storage,bus=usb-bus.0,drive=stick
|
||||
|
||||
Instead of providing a custom Linux kernel via the -kernel command you may also
|
||||
choose to let the Orange Pi PC machine load the bootloader from SD card, just like
|
||||
a real board would do using the BootROM. Simply pass the selected image via the -sd
|
||||
argument and remove the -kernel, -append, -dbt and -initrd arguments:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ qemu-system-arm -M orangepi-pc -nic user -nographic \
|
||||
-sd Armbian_19.11.3_Orangepipc_buster_current_5.3.9.img
|
||||
|
||||
Note that both the official Orange Pi PC images and Armbian images start
|
||||
a lot of userland programs via systemd. Depending on the host hardware and OS,
|
||||
they may be slow to emulate, especially due to emulating the 4 cores.
|
||||
To help reduce the performance slow down due to emulating the 4 cores, you can
|
||||
give the following kernel parameters via U-Boot (or via -append):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
=> setenv extraargs 'systemd.default_timeout_start_sec=9000 loglevel=7 nosmp console=ttyS0,115200'
|
||||
|
||||
Running U-Boot
|
||||
""""""""""""""
|
||||
|
||||
U-Boot mainline can be build and configured using the orangepi_pc_defconfig
|
||||
using similar commands as describe above for Linux. Note that it is recommended
|
||||
for development/testing to select the following configuration setting in U-Boot:
|
||||
|
||||
Device Tree Control > Provider for DTB for DT Control > Embedded DTB
|
||||
|
||||
To start U-Boot using the Orange Pi PC machine, provide the
|
||||
u-boot binary to the -kernel argument:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ qemu-system-arm -M orangepi-pc -nic user -nographic \
|
||||
-kernel /path/to/uboot/u-boot -sd disk.img
|
||||
|
||||
Use the following U-boot commands to load and boot a Linux kernel from SD card:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
=> setenv bootargs console=ttyS0,115200
|
||||
=> ext2load mmc 0 0x42000000 zImage
|
||||
=> ext2load mmc 0 0x43000000 sun8i-h3-orangepi-pc.dtb
|
||||
=> bootz 0x42000000 - 0x43000000
|
||||
|
||||
Running NetBSD
|
||||
""""""""""""""
|
||||
|
||||
The NetBSD operating system also includes support for Allwinner H3 based boards,
|
||||
including the Orange Pi PC. NetBSD 9.0 is known to work best for the Orange Pi PC
|
||||
board and provides a fully working system with serial console, networking and storage.
|
||||
For the Orange Pi PC machine, get the 'evbarm-earmv7hf' based image from:
|
||||
|
||||
https://cdn.netbsd.org/pub/NetBSD/NetBSD-9.0/evbarm-earmv7hf/binary/gzimg/armv7.img.gz
|
||||
|
||||
The image requires manually installing U-Boot in the image. Build U-Boot with
|
||||
the orangepi_pc_defconfig configuration as described in the previous section.
|
||||
Next, unzip the NetBSD image and write the U-Boot binary including SPL using:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ gunzip armv7.img.gz
|
||||
$ dd if=/path/to/u-boot-sunxi-with-spl.bin of=armv7.img bs=1024 seek=8 conv=notrunc
|
||||
|
||||
Finally, before starting the machine the SD image must be extended such
|
||||
that the NetBSD kernel will not conclude the NetBSD partition is larger than
|
||||
the emulated SD card:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ dd if=/dev/zero bs=1M count=64 >> armv7.img
|
||||
|
||||
Start the machine using the following command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ qemu-system-arm -M orangepi-pc -nic user -nographic \
|
||||
-sd armv7.img -global allwinner-rtc.base-year=2000
|
||||
|
||||
At the U-Boot stage, interrupt the automatic boot process by pressing a key
|
||||
and set the following environment variables before booting:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
=> setenv bootargs root=ld0a
|
||||
=> setenv kernel netbsd-GENERIC.ub
|
||||
=> setenv fdtfile dtb/sun8i-h3-orangepi-pc.dtb
|
||||
=> setenv bootcmd 'fatload mmc 0:1 ${kernel_addr_r} ${kernel}; fatload mmc 0:1 ${fdt_addr_r} ${fdtfile}; fdt addr ${fdt_addr_r}; bootm ${kernel_addr_r} - ${fdt_addr_r}'
|
||||
|
||||
Optionally you may save the environment variables to SD card with 'saveenv'.
|
||||
To continue booting simply give the 'boot' command and NetBSD boots.
|
||||
|
||||
Orange Pi PC acceptance tests
|
||||
"""""""""""""""""""""""""""""
|
||||
|
||||
The Orange Pi PC machine has several acceptance tests included.
|
||||
To run the whole set of tests, build QEMU from source and simply
|
||||
provide the following command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ AVOCADO_ALLOW_LARGE_STORAGE=yes avocado --show=app,console run \
|
||||
-t machine:orangepi-pc tests/acceptance/boot_linux_console.py
|
@ -68,6 +68,7 @@ undocumented; you can get a complete list by running
|
||||
``qemu-system-aarch64 --machine help``.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
arm/integratorcp
|
||||
arm/versatile
|
||||
@ -78,6 +79,7 @@ undocumented; you can get a complete list by running
|
||||
arm/stellaris
|
||||
arm/musicpal
|
||||
arm/sx1
|
||||
arm/orangepi
|
||||
|
||||
Arm CPU features
|
||||
================
|
||||
|
@ -297,6 +297,18 @@ config ALLWINNER_A10
|
||||
select SERIAL
|
||||
select UNIMP
|
||||
|
||||
config ALLWINNER_H3
|
||||
bool
|
||||
select ALLWINNER_A10_PIT
|
||||
select ALLWINNER_SUN8I_EMAC
|
||||
select SERIAL
|
||||
select ARM_TIMER
|
||||
select ARM_GIC
|
||||
select UNIMP
|
||||
select USB_OHCI
|
||||
select USB_EHCI_SYSBUS
|
||||
select SD
|
||||
|
||||
config RASPI
|
||||
bool
|
||||
select FRAMEBUFFER
|
||||
|
@ -35,6 +35,7 @@ obj-$(CONFIG_DIGIC) += digic.o
|
||||
obj-$(CONFIG_OMAP) += omap1.o omap2.o
|
||||
obj-$(CONFIG_STRONGARM) += strongarm.o
|
||||
obj-$(CONFIG_ALLWINNER_A10) += allwinner-a10.o cubieboard.o
|
||||
obj-$(CONFIG_ALLWINNER_H3) += allwinner-h3.o orangepi.o
|
||||
obj-$(CONFIG_RASPI) += bcm2835_peripherals.o bcm2836.o raspi.o
|
||||
obj-$(CONFIG_STM32F205_SOC) += stm32f205_soc.o
|
||||
obj-$(CONFIG_STM32F405_SOC) += stm32f405_soc.o
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "hw/boards.h"
|
||||
#include "hw/usb/hcd-ohci.h"
|
||||
|
||||
#define AW_A10_MMC0_BASE 0x01c0f000
|
||||
#define AW_A10_PIC_REG_BASE 0x01c20400
|
||||
#define AW_A10_PIT_REG_BASE 0x01c20c00
|
||||
#define AW_A10_UART0_REG_BASE 0x01c28000
|
||||
@ -34,6 +35,7 @@
|
||||
#define AW_A10_EHCI_BASE 0x01c14000
|
||||
#define AW_A10_OHCI_BASE 0x01c14400
|
||||
#define AW_A10_SATA_BASE 0x01c18000
|
||||
#define AW_A10_RTC_BASE 0x01c20d00
|
||||
|
||||
static void aw_a10_init(Object *obj)
|
||||
{
|
||||
@ -64,6 +66,12 @@ static void aw_a10_init(Object *obj)
|
||||
sizeof(s->ohci[i]), TYPE_SYSBUS_OHCI);
|
||||
}
|
||||
}
|
||||
|
||||
sysbus_init_child_obj(obj, "mmc0", &s->mmc0, sizeof(s->mmc0),
|
||||
TYPE_AW_SDHOST_SUN4I);
|
||||
|
||||
sysbus_init_child_obj(obj, "rtc", &s->rtc, sizeof(s->rtc),
|
||||
TYPE_AW_RTC_SUN4I);
|
||||
}
|
||||
|
||||
static void aw_a10_realize(DeviceState *dev, Error **errp)
|
||||
@ -164,6 +172,17 @@ static void aw_a10_realize(DeviceState *dev, Error **errp)
|
||||
qdev_get_gpio_in(dev, 64 + i));
|
||||
}
|
||||
}
|
||||
|
||||
/* SD/MMC */
|
||||
qdev_init_nofail(DEVICE(&s->mmc0));
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->mmc0), 0, AW_A10_MMC0_BASE);
|
||||
sysbus_connect_irq(SYS_BUS_DEVICE(&s->mmc0), 0, qdev_get_gpio_in(dev, 32));
|
||||
object_property_add_alias(OBJECT(s), "sd-bus", OBJECT(&s->mmc0),
|
||||
"sd-bus", &error_abort);
|
||||
|
||||
/* RTC */
|
||||
qdev_init_nofail(DEVICE(&s->rtc));
|
||||
sysbus_mmio_map_overlap(SYS_BUS_DEVICE(&s->rtc), 0, AW_A10_RTC_BASE, 10);
|
||||
}
|
||||
|
||||
static void aw_a10_class_init(ObjectClass *oc, void *data)
|
||||
|
465
hw/arm/allwinner-h3.c
Normal file
465
hw/arm/allwinner-h3.c
Normal file
@ -0,0 +1,465 @@
|
||||
/*
|
||||
* Allwinner H3 System on Chip emulation
|
||||
*
|
||||
* Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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 "exec/address-spaces.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qemu/units.h"
|
||||
#include "hw/qdev-core.h"
|
||||
#include "cpu.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-h3.h"
|
||||
|
||||
/* Memory map */
|
||||
const hwaddr allwinner_h3_memmap[] = {
|
||||
[AW_H3_SRAM_A1] = 0x00000000,
|
||||
[AW_H3_SRAM_A2] = 0x00044000,
|
||||
[AW_H3_SRAM_C] = 0x00010000,
|
||||
[AW_H3_SYSCTRL] = 0x01c00000,
|
||||
[AW_H3_MMC0] = 0x01c0f000,
|
||||
[AW_H3_SID] = 0x01c14000,
|
||||
[AW_H3_EHCI0] = 0x01c1a000,
|
||||
[AW_H3_OHCI0] = 0x01c1a400,
|
||||
[AW_H3_EHCI1] = 0x01c1b000,
|
||||
[AW_H3_OHCI1] = 0x01c1b400,
|
||||
[AW_H3_EHCI2] = 0x01c1c000,
|
||||
[AW_H3_OHCI2] = 0x01c1c400,
|
||||
[AW_H3_EHCI3] = 0x01c1d000,
|
||||
[AW_H3_OHCI3] = 0x01c1d400,
|
||||
[AW_H3_CCU] = 0x01c20000,
|
||||
[AW_H3_PIT] = 0x01c20c00,
|
||||
[AW_H3_UART0] = 0x01c28000,
|
||||
[AW_H3_UART1] = 0x01c28400,
|
||||
[AW_H3_UART2] = 0x01c28800,
|
||||
[AW_H3_UART3] = 0x01c28c00,
|
||||
[AW_H3_EMAC] = 0x01c30000,
|
||||
[AW_H3_DRAMCOM] = 0x01c62000,
|
||||
[AW_H3_DRAMCTL] = 0x01c63000,
|
||||
[AW_H3_DRAMPHY] = 0x01c65000,
|
||||
[AW_H3_GIC_DIST] = 0x01c81000,
|
||||
[AW_H3_GIC_CPU] = 0x01c82000,
|
||||
[AW_H3_GIC_HYP] = 0x01c84000,
|
||||
[AW_H3_GIC_VCPU] = 0x01c86000,
|
||||
[AW_H3_RTC] = 0x01f00000,
|
||||
[AW_H3_CPUCFG] = 0x01f01c00,
|
||||
[AW_H3_SDRAM] = 0x40000000
|
||||
};
|
||||
|
||||
/* List of unimplemented devices */
|
||||
struct AwH3Unimplemented {
|
||||
const char *device_name;
|
||||
hwaddr base;
|
||||
hwaddr size;
|
||||
} unimplemented[] = {
|
||||
{ "d-engine", 0x01000000, 4 * MiB },
|
||||
{ "d-inter", 0x01400000, 128 * KiB },
|
||||
{ "dma", 0x01c02000, 4 * KiB },
|
||||
{ "nfdc", 0x01c03000, 4 * KiB },
|
||||
{ "ts", 0x01c06000, 4 * KiB },
|
||||
{ "keymem", 0x01c0b000, 4 * KiB },
|
||||
{ "lcd0", 0x01c0c000, 4 * KiB },
|
||||
{ "lcd1", 0x01c0d000, 4 * KiB },
|
||||
{ "ve", 0x01c0e000, 4 * KiB },
|
||||
{ "mmc1", 0x01c10000, 4 * KiB },
|
||||
{ "mmc2", 0x01c11000, 4 * KiB },
|
||||
{ "crypto", 0x01c15000, 4 * KiB },
|
||||
{ "msgbox", 0x01c17000, 4 * KiB },
|
||||
{ "spinlock", 0x01c18000, 4 * KiB },
|
||||
{ "usb0-otg", 0x01c19000, 4 * KiB },
|
||||
{ "usb0-phy", 0x01c1a000, 4 * KiB },
|
||||
{ "usb1-phy", 0x01c1b000, 4 * KiB },
|
||||
{ "usb2-phy", 0x01c1c000, 4 * KiB },
|
||||
{ "usb3-phy", 0x01c1d000, 4 * KiB },
|
||||
{ "smc", 0x01c1e000, 4 * KiB },
|
||||
{ "pio", 0x01c20800, 1 * KiB },
|
||||
{ "owa", 0x01c21000, 1 * KiB },
|
||||
{ "pwm", 0x01c21400, 1 * KiB },
|
||||
{ "keyadc", 0x01c21800, 1 * KiB },
|
||||
{ "pcm0", 0x01c22000, 1 * KiB },
|
||||
{ "pcm1", 0x01c22400, 1 * KiB },
|
||||
{ "pcm2", 0x01c22800, 1 * KiB },
|
||||
{ "audio", 0x01c22c00, 2 * KiB },
|
||||
{ "smta", 0x01c23400, 1 * KiB },
|
||||
{ "ths", 0x01c25000, 1 * KiB },
|
||||
{ "uart0", 0x01c28000, 1 * KiB },
|
||||
{ "uart1", 0x01c28400, 1 * KiB },
|
||||
{ "uart2", 0x01c28800, 1 * KiB },
|
||||
{ "uart3", 0x01c28c00, 1 * KiB },
|
||||
{ "twi0", 0x01c2ac00, 1 * KiB },
|
||||
{ "twi1", 0x01c2b000, 1 * KiB },
|
||||
{ "twi2", 0x01c2b400, 1 * KiB },
|
||||
{ "scr", 0x01c2c400, 1 * KiB },
|
||||
{ "gpu", 0x01c40000, 64 * KiB },
|
||||
{ "hstmr", 0x01c60000, 4 * KiB },
|
||||
{ "spi0", 0x01c68000, 4 * KiB },
|
||||
{ "spi1", 0x01c69000, 4 * KiB },
|
||||
{ "csi", 0x01cb0000, 320 * KiB },
|
||||
{ "tve", 0x01e00000, 64 * KiB },
|
||||
{ "hdmi", 0x01ee0000, 128 * KiB },
|
||||
{ "r_timer", 0x01f00800, 1 * KiB },
|
||||
{ "r_intc", 0x01f00c00, 1 * KiB },
|
||||
{ "r_wdog", 0x01f01000, 1 * KiB },
|
||||
{ "r_prcm", 0x01f01400, 1 * KiB },
|
||||
{ "r_twd", 0x01f01800, 1 * KiB },
|
||||
{ "r_cir-rx", 0x01f02000, 1 * KiB },
|
||||
{ "r_twi", 0x01f02400, 1 * KiB },
|
||||
{ "r_uart", 0x01f02800, 1 * KiB },
|
||||
{ "r_pio", 0x01f02c00, 1 * KiB },
|
||||
{ "r_pwm", 0x01f03800, 1 * KiB },
|
||||
{ "core-dbg", 0x3f500000, 128 * KiB },
|
||||
{ "tsgen-ro", 0x3f506000, 4 * KiB },
|
||||
{ "tsgen-ctl", 0x3f507000, 4 * KiB },
|
||||
{ "ddr-mem", 0x40000000, 2 * GiB },
|
||||
{ "n-brom", 0xffff0000, 32 * KiB },
|
||||
{ "s-brom", 0xffff0000, 64 * KiB }
|
||||
};
|
||||
|
||||
/* Per Processor Interrupts */
|
||||
enum {
|
||||
AW_H3_GIC_PPI_MAINT = 9,
|
||||
AW_H3_GIC_PPI_HYPTIMER = 10,
|
||||
AW_H3_GIC_PPI_VIRTTIMER = 11,
|
||||
AW_H3_GIC_PPI_SECTIMER = 13,
|
||||
AW_H3_GIC_PPI_PHYSTIMER = 14
|
||||
};
|
||||
|
||||
/* Shared Processor Interrupts */
|
||||
enum {
|
||||
AW_H3_GIC_SPI_UART0 = 0,
|
||||
AW_H3_GIC_SPI_UART1 = 1,
|
||||
AW_H3_GIC_SPI_UART2 = 2,
|
||||
AW_H3_GIC_SPI_UART3 = 3,
|
||||
AW_H3_GIC_SPI_TIMER0 = 18,
|
||||
AW_H3_GIC_SPI_TIMER1 = 19,
|
||||
AW_H3_GIC_SPI_MMC0 = 60,
|
||||
AW_H3_GIC_SPI_EHCI0 = 72,
|
||||
AW_H3_GIC_SPI_OHCI0 = 73,
|
||||
AW_H3_GIC_SPI_EHCI1 = 74,
|
||||
AW_H3_GIC_SPI_OHCI1 = 75,
|
||||
AW_H3_GIC_SPI_EHCI2 = 76,
|
||||
AW_H3_GIC_SPI_OHCI2 = 77,
|
||||
AW_H3_GIC_SPI_EHCI3 = 78,
|
||||
AW_H3_GIC_SPI_OHCI3 = 79,
|
||||
AW_H3_GIC_SPI_EMAC = 82
|
||||
};
|
||||
|
||||
/* Allwinner H3 general constants */
|
||||
enum {
|
||||
AW_H3_GIC_NUM_SPI = 128
|
||||
};
|
||||
|
||||
void allwinner_h3_bootrom_setup(AwH3State *s, BlockBackend *blk)
|
||||
{
|
||||
const int64_t rom_size = 32 * KiB;
|
||||
g_autofree uint8_t *buffer = g_new0(uint8_t, rom_size);
|
||||
|
||||
if (blk_pread(blk, 8 * KiB, buffer, rom_size) < 0) {
|
||||
error_setg(&error_fatal, "%s: failed to read BlockBackend data",
|
||||
__func__);
|
||||
return;
|
||||
}
|
||||
|
||||
rom_add_blob("allwinner-h3.bootrom", buffer, rom_size,
|
||||
rom_size, s->memmap[AW_H3_SRAM_A1],
|
||||
NULL, NULL, NULL, NULL, false);
|
||||
}
|
||||
|
||||
static void allwinner_h3_init(Object *obj)
|
||||
{
|
||||
AwH3State *s = AW_H3(obj);
|
||||
|
||||
s->memmap = allwinner_h3_memmap;
|
||||
|
||||
for (int i = 0; i < AW_H3_NUM_CPUS; i++) {
|
||||
object_initialize_child(obj, "cpu[*]", &s->cpus[i], sizeof(s->cpus[i]),
|
||||
ARM_CPU_TYPE_NAME("cortex-a7"),
|
||||
&error_abort, NULL);
|
||||
}
|
||||
|
||||
sysbus_init_child_obj(obj, "gic", &s->gic, sizeof(s->gic),
|
||||
TYPE_ARM_GIC);
|
||||
|
||||
sysbus_init_child_obj(obj, "timer", &s->timer, sizeof(s->timer),
|
||||
TYPE_AW_A10_PIT);
|
||||
object_property_add_alias(obj, "clk0-freq", OBJECT(&s->timer),
|
||||
"clk0-freq", &error_abort);
|
||||
object_property_add_alias(obj, "clk1-freq", OBJECT(&s->timer),
|
||||
"clk1-freq", &error_abort);
|
||||
|
||||
sysbus_init_child_obj(obj, "ccu", &s->ccu, sizeof(s->ccu),
|
||||
TYPE_AW_H3_CCU);
|
||||
|
||||
sysbus_init_child_obj(obj, "sysctrl", &s->sysctrl, sizeof(s->sysctrl),
|
||||
TYPE_AW_H3_SYSCTRL);
|
||||
|
||||
sysbus_init_child_obj(obj, "cpucfg", &s->cpucfg, sizeof(s->cpucfg),
|
||||
TYPE_AW_CPUCFG);
|
||||
|
||||
sysbus_init_child_obj(obj, "sid", &s->sid, sizeof(s->sid),
|
||||
TYPE_AW_SID);
|
||||
object_property_add_alias(obj, "identifier", OBJECT(&s->sid),
|
||||
"identifier", &error_abort);
|
||||
|
||||
sysbus_init_child_obj(obj, "mmc0", &s->mmc0, sizeof(s->mmc0),
|
||||
TYPE_AW_SDHOST_SUN5I);
|
||||
|
||||
sysbus_init_child_obj(obj, "emac", &s->emac, sizeof(s->emac),
|
||||
TYPE_AW_SUN8I_EMAC);
|
||||
|
||||
sysbus_init_child_obj(obj, "dramc", &s->dramc, sizeof(s->dramc),
|
||||
TYPE_AW_H3_DRAMC);
|
||||
object_property_add_alias(obj, "ram-addr", OBJECT(&s->dramc),
|
||||
"ram-addr", &error_abort);
|
||||
object_property_add_alias(obj, "ram-size", OBJECT(&s->dramc),
|
||||
"ram-size", &error_abort);
|
||||
|
||||
sysbus_init_child_obj(obj, "rtc", &s->rtc, sizeof(s->rtc),
|
||||
TYPE_AW_RTC_SUN6I);
|
||||
}
|
||||
|
||||
static void allwinner_h3_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
AwH3State *s = AW_H3(dev);
|
||||
unsigned i;
|
||||
|
||||
/* CPUs */
|
||||
for (i = 0; i < AW_H3_NUM_CPUS; i++) {
|
||||
|
||||
/* Provide Power State Coordination Interface */
|
||||
qdev_prop_set_int32(DEVICE(&s->cpus[i]), "psci-conduit",
|
||||
QEMU_PSCI_CONDUIT_HVC);
|
||||
|
||||
/* Disable secondary CPUs */
|
||||
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_init_nofail(DEVICE(&s->cpus[i]));
|
||||
}
|
||||
|
||||
/* Generic Interrupt Controller */
|
||||
qdev_prop_set_uint32(DEVICE(&s->gic), "num-irq", AW_H3_GIC_NUM_SPI +
|
||||
GIC_INTERNAL);
|
||||
qdev_prop_set_uint32(DEVICE(&s->gic), "revision", 2);
|
||||
qdev_prop_set_uint32(DEVICE(&s->gic), "num-cpu", AW_H3_NUM_CPUS);
|
||||
qdev_prop_set_bit(DEVICE(&s->gic), "has-security-extensions", false);
|
||||
qdev_prop_set_bit(DEVICE(&s->gic), "has-virtualization-extensions", true);
|
||||
qdev_init_nofail(DEVICE(&s->gic));
|
||||
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->gic), 0, s->memmap[AW_H3_GIC_DIST]);
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->gic), 1, s->memmap[AW_H3_GIC_CPU]);
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->gic), 2, s->memmap[AW_H3_GIC_HYP]);
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->gic), 3, s->memmap[AW_H3_GIC_VCPU]);
|
||||
|
||||
/*
|
||||
* Wire the outputs from each CPU's generic timer and the GICv3
|
||||
* 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_H3_NUM_CPUS; i++) {
|
||||
DeviceState *cpudev = DEVICE(&s->cpus[i]);
|
||||
int ppibase = AW_H3_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_H3_GIC_PPI_PHYSTIMER,
|
||||
[GTIMER_VIRT] = AW_H3_GIC_PPI_VIRTTIMER,
|
||||
[GTIMER_HYP] = AW_H3_GIC_PPI_HYPTIMER,
|
||||
[GTIMER_SEC] = AW_H3_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_H3_NUM_CPUS,
|
||||
qdev_get_gpio_in(cpudev, ARM_CPU_FIQ));
|
||||
sysbus_connect_irq(SYS_BUS_DEVICE(&s->gic), i + (2 * AW_H3_NUM_CPUS),
|
||||
qdev_get_gpio_in(cpudev, ARM_CPU_VIRQ));
|
||||
sysbus_connect_irq(SYS_BUS_DEVICE(&s->gic), i + (3 * AW_H3_NUM_CPUS),
|
||||
qdev_get_gpio_in(cpudev, ARM_CPU_VFIQ));
|
||||
|
||||
/* GIC maintenance signal */
|
||||
sysbus_connect_irq(SYS_BUS_DEVICE(&s->gic), i + (4 * AW_H3_NUM_CPUS),
|
||||
qdev_get_gpio_in(DEVICE(&s->gic),
|
||||
ppibase + AW_H3_GIC_PPI_MAINT));
|
||||
}
|
||||
|
||||
/* Timer */
|
||||
qdev_init_nofail(DEVICE(&s->timer));
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->timer), 0, s->memmap[AW_H3_PIT]);
|
||||
sysbus_connect_irq(SYS_BUS_DEVICE(&s->timer), 0,
|
||||
qdev_get_gpio_in(DEVICE(&s->gic), AW_H3_GIC_SPI_TIMER0));
|
||||
sysbus_connect_irq(SYS_BUS_DEVICE(&s->timer), 1,
|
||||
qdev_get_gpio_in(DEVICE(&s->gic), AW_H3_GIC_SPI_TIMER1));
|
||||
|
||||
/* SRAM */
|
||||
memory_region_init_ram(&s->sram_a1, OBJECT(dev), "sram A1",
|
||||
64 * KiB, &error_abort);
|
||||
memory_region_init_ram(&s->sram_a2, OBJECT(dev), "sram A2",
|
||||
32 * KiB, &error_abort);
|
||||
memory_region_init_ram(&s->sram_c, OBJECT(dev), "sram C",
|
||||
44 * KiB, &error_abort);
|
||||
memory_region_add_subregion(get_system_memory(), s->memmap[AW_H3_SRAM_A1],
|
||||
&s->sram_a1);
|
||||
memory_region_add_subregion(get_system_memory(), s->memmap[AW_H3_SRAM_A2],
|
||||
&s->sram_a2);
|
||||
memory_region_add_subregion(get_system_memory(), s->memmap[AW_H3_SRAM_C],
|
||||
&s->sram_c);
|
||||
|
||||
/* Clock Control Unit */
|
||||
qdev_init_nofail(DEVICE(&s->ccu));
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->ccu), 0, s->memmap[AW_H3_CCU]);
|
||||
|
||||
/* System Control */
|
||||
qdev_init_nofail(DEVICE(&s->sysctrl));
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->sysctrl), 0, s->memmap[AW_H3_SYSCTRL]);
|
||||
|
||||
/* CPU Configuration */
|
||||
qdev_init_nofail(DEVICE(&s->cpucfg));
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->cpucfg), 0, s->memmap[AW_H3_CPUCFG]);
|
||||
|
||||
/* Security Identifier */
|
||||
qdev_init_nofail(DEVICE(&s->sid));
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->sid), 0, s->memmap[AW_H3_SID]);
|
||||
|
||||
/* SD/MMC */
|
||||
qdev_init_nofail(DEVICE(&s->mmc0));
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->mmc0), 0, s->memmap[AW_H3_MMC0]);
|
||||
sysbus_connect_irq(SYS_BUS_DEVICE(&s->mmc0), 0,
|
||||
qdev_get_gpio_in(DEVICE(&s->gic), AW_H3_GIC_SPI_MMC0));
|
||||
|
||||
object_property_add_alias(OBJECT(s), "sd-bus", OBJECT(&s->mmc0),
|
||||
"sd-bus", &error_abort);
|
||||
|
||||
/* EMAC */
|
||||
if (nd_table[0].used) {
|
||||
qemu_check_nic_model(&nd_table[0], TYPE_AW_SUN8I_EMAC);
|
||||
qdev_set_nic_properties(DEVICE(&s->emac), &nd_table[0]);
|
||||
}
|
||||
qdev_init_nofail(DEVICE(&s->emac));
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->emac), 0, s->memmap[AW_H3_EMAC]);
|
||||
sysbus_connect_irq(SYS_BUS_DEVICE(&s->emac), 0,
|
||||
qdev_get_gpio_in(DEVICE(&s->gic), AW_H3_GIC_SPI_EMAC));
|
||||
|
||||
/* Universal Serial Bus */
|
||||
sysbus_create_simple(TYPE_AW_H3_EHCI, s->memmap[AW_H3_EHCI0],
|
||||
qdev_get_gpio_in(DEVICE(&s->gic),
|
||||
AW_H3_GIC_SPI_EHCI0));
|
||||
sysbus_create_simple(TYPE_AW_H3_EHCI, s->memmap[AW_H3_EHCI1],
|
||||
qdev_get_gpio_in(DEVICE(&s->gic),
|
||||
AW_H3_GIC_SPI_EHCI1));
|
||||
sysbus_create_simple(TYPE_AW_H3_EHCI, s->memmap[AW_H3_EHCI2],
|
||||
qdev_get_gpio_in(DEVICE(&s->gic),
|
||||
AW_H3_GIC_SPI_EHCI2));
|
||||
sysbus_create_simple(TYPE_AW_H3_EHCI, s->memmap[AW_H3_EHCI3],
|
||||
qdev_get_gpio_in(DEVICE(&s->gic),
|
||||
AW_H3_GIC_SPI_EHCI3));
|
||||
|
||||
sysbus_create_simple("sysbus-ohci", s->memmap[AW_H3_OHCI0],
|
||||
qdev_get_gpio_in(DEVICE(&s->gic),
|
||||
AW_H3_GIC_SPI_OHCI0));
|
||||
sysbus_create_simple("sysbus-ohci", s->memmap[AW_H3_OHCI1],
|
||||
qdev_get_gpio_in(DEVICE(&s->gic),
|
||||
AW_H3_GIC_SPI_OHCI1));
|
||||
sysbus_create_simple("sysbus-ohci", s->memmap[AW_H3_OHCI2],
|
||||
qdev_get_gpio_in(DEVICE(&s->gic),
|
||||
AW_H3_GIC_SPI_OHCI2));
|
||||
sysbus_create_simple("sysbus-ohci", s->memmap[AW_H3_OHCI3],
|
||||
qdev_get_gpio_in(DEVICE(&s->gic),
|
||||
AW_H3_GIC_SPI_OHCI3));
|
||||
|
||||
/* UART0. For future clocktree API: All UARTS are connected to APB2_CLK. */
|
||||
serial_mm_init(get_system_memory(), s->memmap[AW_H3_UART0], 2,
|
||||
qdev_get_gpio_in(DEVICE(&s->gic), AW_H3_GIC_SPI_UART0),
|
||||
115200, serial_hd(0), DEVICE_NATIVE_ENDIAN);
|
||||
/* UART1 */
|
||||
serial_mm_init(get_system_memory(), s->memmap[AW_H3_UART1], 2,
|
||||
qdev_get_gpio_in(DEVICE(&s->gic), AW_H3_GIC_SPI_UART1),
|
||||
115200, serial_hd(1), DEVICE_NATIVE_ENDIAN);
|
||||
/* UART2 */
|
||||
serial_mm_init(get_system_memory(), s->memmap[AW_H3_UART2], 2,
|
||||
qdev_get_gpio_in(DEVICE(&s->gic), AW_H3_GIC_SPI_UART2),
|
||||
115200, serial_hd(2), DEVICE_NATIVE_ENDIAN);
|
||||
/* UART3 */
|
||||
serial_mm_init(get_system_memory(), s->memmap[AW_H3_UART3], 2,
|
||||
qdev_get_gpio_in(DEVICE(&s->gic), AW_H3_GIC_SPI_UART3),
|
||||
115200, serial_hd(3), DEVICE_NATIVE_ENDIAN);
|
||||
|
||||
/* DRAMC */
|
||||
qdev_init_nofail(DEVICE(&s->dramc));
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->dramc), 0, s->memmap[AW_H3_DRAMCOM]);
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->dramc), 1, s->memmap[AW_H3_DRAMCTL]);
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->dramc), 2, s->memmap[AW_H3_DRAMPHY]);
|
||||
|
||||
/* RTC */
|
||||
qdev_init_nofail(DEVICE(&s->rtc));
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->rtc), 0, s->memmap[AW_H3_RTC]);
|
||||
|
||||
/* Unimplemented devices */
|
||||
for (i = 0; i < ARRAY_SIZE(unimplemented); i++) {
|
||||
create_unimplemented_device(unimplemented[i].device_name,
|
||||
unimplemented[i].base,
|
||||
unimplemented[i].size);
|
||||
}
|
||||
}
|
||||
|
||||
static void allwinner_h3_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(oc);
|
||||
|
||||
dc->realize = allwinner_h3_realize;
|
||||
/* Reason: uses serial_hd() in realize function */
|
||||
dc->user_creatable = false;
|
||||
}
|
||||
|
||||
static const TypeInfo allwinner_h3_type_info = {
|
||||
.name = TYPE_AW_H3,
|
||||
.parent = TYPE_DEVICE,
|
||||
.instance_size = sizeof(AwH3State),
|
||||
.instance_init = allwinner_h3_init,
|
||||
.class_init = allwinner_h3_class_init,
|
||||
};
|
||||
|
||||
static void allwinner_h3_register_types(void)
|
||||
{
|
||||
type_register_static(&allwinner_h3_type_info);
|
||||
}
|
||||
|
||||
type_init(allwinner_h3_register_types)
|
@ -22,6 +22,7 @@
|
||||
#include "sysemu/sysemu.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "hw/boards.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "hw/arm/allwinner-a10.h"
|
||||
|
||||
static struct arm_boot_info cubieboard_binfo = {
|
||||
@ -33,6 +34,10 @@ static void cubieboard_init(MachineState *machine)
|
||||
{
|
||||
AwA10State *a10;
|
||||
Error *err = NULL;
|
||||
DriveInfo *di;
|
||||
BlockBackend *blk;
|
||||
BusState *bus;
|
||||
DeviceState *carddev;
|
||||
|
||||
/* BIOS is not supported by this board */
|
||||
if (bios_name) {
|
||||
@ -54,6 +59,9 @@ static void cubieboard_init(MachineState *machine)
|
||||
}
|
||||
|
||||
a10 = AW_A10(object_new(TYPE_AW_A10));
|
||||
object_property_add_child(OBJECT(machine), "soc", OBJECT(a10),
|
||||
&error_abort);
|
||||
object_unref(OBJECT(a10));
|
||||
|
||||
object_property_set_int(OBJECT(&a10->emac), 1, "phy-addr", &err);
|
||||
if (err != NULL) {
|
||||
@ -79,6 +87,16 @@ static void cubieboard_init(MachineState *machine)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Retrieve SD bus */
|
||||
di = drive_get_next(IF_SD);
|
||||
blk = di ? blk_by_legacy_dinfo(di) : NULL;
|
||||
bus = qdev_get_child_bus(DEVICE(a10), "sd-bus");
|
||||
|
||||
/* Plug in SD card */
|
||||
carddev = qdev_create(bus, TYPE_SD_CARD);
|
||||
qdev_prop_set_drive(carddev, "drive", blk, &error_fatal);
|
||||
object_property_set_bool(OBJECT(carddev), true, "realized", &error_fatal);
|
||||
|
||||
memory_region_add_subregion(get_system_memory(), AW_A10_SDRAM_BASE,
|
||||
machine->ram);
|
||||
|
||||
|
@ -31,6 +31,8 @@
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "chardev/char.h"
|
||||
|
||||
#define IMX25_ESDHC_CAPABILITIES 0x07e20000
|
||||
|
||||
static void fsl_imx25_init(Object *obj)
|
||||
{
|
||||
FslIMX25State *s = FSL_IMX25(obj);
|
||||
@ -74,6 +76,17 @@ static void fsl_imx25_init(Object *obj)
|
||||
sysbus_init_child_obj(obj, "gpio[*]", &s->gpio[i], sizeof(s->gpio[i]),
|
||||
TYPE_IMX_GPIO);
|
||||
}
|
||||
|
||||
for (i = 0; i < FSL_IMX25_NUM_ESDHCS; i++) {
|
||||
sysbus_init_child_obj(obj, "sdhc[*]", &s->esdhc[i], sizeof(s->esdhc[i]),
|
||||
TYPE_IMX_USDHC);
|
||||
}
|
||||
|
||||
for (i = 0; i < FSL_IMX25_NUM_USBS; i++) {
|
||||
sysbus_init_child_obj(obj, "usb[*]", &s->usb[i], sizeof(s->usb[i]),
|
||||
TYPE_CHIPIDEA);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void fsl_imx25_realize(DeviceState *dev, Error **errp)
|
||||
@ -246,6 +259,49 @@ static void fsl_imx25_realize(DeviceState *dev, Error **errp)
|
||||
gpio_table[i].irq));
|
||||
}
|
||||
|
||||
/* Initialize all SDHC */
|
||||
for (i = 0; i < FSL_IMX25_NUM_ESDHCS; i++) {
|
||||
static const struct {
|
||||
hwaddr addr;
|
||||
unsigned int irq;
|
||||
} esdhc_table[FSL_IMX25_NUM_ESDHCS] = {
|
||||
{ FSL_IMX25_ESDHC1_ADDR, FSL_IMX25_ESDHC1_IRQ },
|
||||
{ FSL_IMX25_ESDHC2_ADDR, FSL_IMX25_ESDHC2_IRQ },
|
||||
};
|
||||
|
||||
object_property_set_uint(OBJECT(&s->esdhc[i]), 2, "sd-spec-version",
|
||||
&err);
|
||||
object_property_set_uint(OBJECT(&s->esdhc[i]), IMX25_ESDHC_CAPABILITIES,
|
||||
"capareg", &err);
|
||||
object_property_set_bool(OBJECT(&s->esdhc[i]), true, "realized", &err);
|
||||
if (err) {
|
||||
error_propagate(errp, err);
|
||||
return;
|
||||
}
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->esdhc[i]), 0, esdhc_table[i].addr);
|
||||
sysbus_connect_irq(SYS_BUS_DEVICE(&s->esdhc[i]), 0,
|
||||
qdev_get_gpio_in(DEVICE(&s->avic),
|
||||
esdhc_table[i].irq));
|
||||
}
|
||||
|
||||
/* USB */
|
||||
for (i = 0; i < FSL_IMX25_NUM_USBS; i++) {
|
||||
static const struct {
|
||||
hwaddr addr;
|
||||
unsigned int irq;
|
||||
} usb_table[FSL_IMX25_NUM_USBS] = {
|
||||
{ FSL_IMX25_USB1_ADDR, FSL_IMX25_USB1_IRQ },
|
||||
{ FSL_IMX25_USB2_ADDR, FSL_IMX25_USB2_IRQ },
|
||||
};
|
||||
|
||||
object_property_set_bool(OBJECT(&s->usb[i]), true, "realized",
|
||||
&error_abort);
|
||||
sysbus_mmio_map(SYS_BUS_DEVICE(&s->usb[i]), 0, usb_table[i].addr);
|
||||
sysbus_connect_irq(SYS_BUS_DEVICE(&s->usb[i]), 0,
|
||||
qdev_get_gpio_in(DEVICE(&s->avic),
|
||||
usb_table[i].irq));
|
||||
}
|
||||
|
||||
/* initialize 2 x 16 KB ROM */
|
||||
memory_region_init_rom(&s->rom[0], NULL,
|
||||
"imx25.rom0", FSL_IMX25_ROM0_SIZE, &err);
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "cpu.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "hw/arm/fsl-imx25.h"
|
||||
#include "hw/boards.h"
|
||||
#include "qemu/error-report.h"
|
||||
@ -120,6 +121,21 @@ static void imx25_pdk_init(MachineState *machine)
|
||||
imx25_pdk_binfo.board_id = 1771,
|
||||
imx25_pdk_binfo.nb_cpus = 1;
|
||||
|
||||
for (i = 0; i < FSL_IMX25_NUM_ESDHCS; i++) {
|
||||
BusState *bus;
|
||||
DeviceState *carddev;
|
||||
DriveInfo *di;
|
||||
BlockBackend *blk;
|
||||
|
||||
di = drive_get_next(IF_SD);
|
||||
blk = di ? blk_by_legacy_dinfo(di) : NULL;
|
||||
bus = qdev_get_child_bus(DEVICE(&s->soc.esdhc[i]), "sd-bus");
|
||||
carddev = qdev_create(bus, TYPE_SD_CARD);
|
||||
qdev_prop_set_drive(carddev, "drive", blk, &error_fatal);
|
||||
object_property_set_bool(OBJECT(carddev), true,
|
||||
"realized", &error_fatal);
|
||||
}
|
||||
|
||||
/*
|
||||
* We test explicitly for qtest here as it is not done (yet?) in
|
||||
* arm_load_kernel(). Without this the "make check" command would
|
||||
|
130
hw/arm/orangepi.c
Normal file
130
hw/arm/orangepi.c
Normal file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Orange Pi emulation
|
||||
*
|
||||
* Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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 "cpu.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "hw/boards.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "hw/arm/allwinner-h3.h"
|
||||
#include "sysemu/sysemu.h"
|
||||
|
||||
static struct arm_boot_info orangepi_binfo = {
|
||||
.nb_cpus = AW_H3_NUM_CPUS,
|
||||
};
|
||||
|
||||
static void orangepi_init(MachineState *machine)
|
||||
{
|
||||
AwH3State *h3;
|
||||
DriveInfo *di;
|
||||
BlockBackend *blk;
|
||||
BusState *bus;
|
||||
DeviceState *carddev;
|
||||
|
||||
/* BIOS is not supported by this board */
|
||||
if (bios_name) {
|
||||
error_report("BIOS not supported for this machine");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* This board has fixed size RAM */
|
||||
if (machine->ram_size != 1 * GiB) {
|
||||
error_report("This machine can only be used with 1GiB of RAM");
|
||||
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);
|
||||
}
|
||||
|
||||
h3 = AW_H3(object_new(TYPE_AW_H3));
|
||||
object_property_add_child(OBJECT(machine), "soc", OBJECT(h3),
|
||||
&error_abort);
|
||||
object_unref(OBJECT(h3));
|
||||
|
||||
/* Setup timer properties */
|
||||
object_property_set_int(OBJECT(h3), 32768, "clk0-freq",
|
||||
&error_abort);
|
||||
object_property_set_int(OBJECT(h3), 24 * 1000 * 1000, "clk1-freq",
|
||||
&error_abort);
|
||||
|
||||
/* Setup SID properties. Currently using a default fixed SID identifier. */
|
||||
if (qemu_uuid_is_null(&h3->sid.identifier)) {
|
||||
qdev_prop_set_string(DEVICE(h3), "identifier",
|
||||
"02c00081-1111-2222-3333-000044556677");
|
||||
} else if (ldl_be_p(&h3->sid.identifier.data[0]) != 0x02c00081) {
|
||||
warn_report("Security Identifier value does not include H3 prefix");
|
||||
}
|
||||
|
||||
/* Setup EMAC properties */
|
||||
object_property_set_int(OBJECT(&h3->emac), 1, "phy-addr", &error_abort);
|
||||
|
||||
/* DRAMC */
|
||||
object_property_set_uint(OBJECT(h3), h3->memmap[AW_H3_SDRAM],
|
||||
"ram-addr", &error_abort);
|
||||
object_property_set_int(OBJECT(h3), machine->ram_size / MiB, "ram-size",
|
||||
&error_abort);
|
||||
|
||||
/* Mark H3 object realized */
|
||||
object_property_set_bool(OBJECT(h3), true, "realized", &error_abort);
|
||||
|
||||
/* Retrieve SD bus */
|
||||
di = drive_get_next(IF_SD);
|
||||
blk = di ? blk_by_legacy_dinfo(di) : NULL;
|
||||
bus = qdev_get_child_bus(DEVICE(h3), "sd-bus");
|
||||
|
||||
/* Plug in SD card */
|
||||
carddev = qdev_create(bus, TYPE_SD_CARD);
|
||||
qdev_prop_set_drive(carddev, "drive", blk, &error_fatal);
|
||||
object_property_set_bool(OBJECT(carddev), true, "realized", &error_fatal);
|
||||
|
||||
/* SDRAM */
|
||||
memory_region_add_subregion(get_system_memory(), h3->memmap[AW_H3_SDRAM],
|
||||
machine->ram);
|
||||
|
||||
/* Load target kernel or start using BootROM */
|
||||
if (!machine->kernel_filename && blk_is_available(blk)) {
|
||||
/* Use Boot ROM to copy data from SD card to SRAM */
|
||||
allwinner_h3_bootrom_setup(h3, blk);
|
||||
}
|
||||
orangepi_binfo.loader_start = h3->memmap[AW_H3_SDRAM];
|
||||
orangepi_binfo.ram_size = machine->ram_size;
|
||||
arm_load_kernel(ARM_CPU(first_cpu), machine, &orangepi_binfo);
|
||||
}
|
||||
|
||||
static void orangepi_machine_init(MachineClass *mc)
|
||||
{
|
||||
mc->desc = "Orange Pi PC";
|
||||
mc->init = orangepi_init;
|
||||
mc->block_default_type = IF_SD;
|
||||
mc->units_per_default_bus = 1;
|
||||
mc->min_cpus = AW_H3_NUM_CPUS;
|
||||
mc->max_cpus = AW_H3_NUM_CPUS;
|
||||
mc->default_cpus = AW_H3_NUM_CPUS;
|
||||
mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-a7");
|
||||
mc->default_ram_size = 1 * GiB;
|
||||
mc->default_ram_id = "orangepi.ram";
|
||||
}
|
||||
|
||||
DEFINE_MACHINE("orangepi-pc", orangepi_machine_init)
|
145
hw/arm/virt.c
145
hw/arm/virt.c
@ -299,7 +299,7 @@ static void fdt_add_timer_nodes(const VirtMachineState *vms)
|
||||
irqflags = GIC_FDT_IRQ_FLAGS_EDGE_LO_HI;
|
||||
}
|
||||
|
||||
if (vms->gic_version == 2) {
|
||||
if (vms->gic_version == VIRT_GIC_VERSION_2) {
|
||||
irqflags = deposit32(irqflags, GIC_FDT_IRQ_PPI_CPU_START,
|
||||
GIC_FDT_IRQ_PPI_CPU_WIDTH,
|
||||
(1 << vms->smp_cpus) - 1);
|
||||
@ -440,7 +440,7 @@ static void fdt_add_gic_node(VirtMachineState *vms)
|
||||
qemu_fdt_setprop_cell(vms->fdt, nodename, "#address-cells", 0x2);
|
||||
qemu_fdt_setprop_cell(vms->fdt, nodename, "#size-cells", 0x2);
|
||||
qemu_fdt_setprop(vms->fdt, nodename, "ranges", NULL, 0);
|
||||
if (vms->gic_version == 3) {
|
||||
if (vms->gic_version == VIRT_GIC_VERSION_3) {
|
||||
int nb_redist_regions = virt_gicv3_redist_region_count(vms);
|
||||
|
||||
qemu_fdt_setprop_string(vms->fdt, nodename, "compatible",
|
||||
@ -519,7 +519,7 @@ static void fdt_add_pmu_nodes(const VirtMachineState *vms)
|
||||
}
|
||||
}
|
||||
|
||||
if (vms->gic_version == 2) {
|
||||
if (vms->gic_version == VIRT_GIC_VERSION_2) {
|
||||
irqflags = deposit32(irqflags, GIC_FDT_IRQ_PPI_CPU_START,
|
||||
GIC_FDT_IRQ_PPI_CPU_WIDTH,
|
||||
(1 << vms->smp_cpus) - 1);
|
||||
@ -1470,7 +1470,7 @@ static uint64_t virt_cpu_mp_affinity(VirtMachineState *vms, int idx)
|
||||
* purposes are to make TCG consistent (with 64-bit KVM hosts)
|
||||
* and to improve SGI efficiency.
|
||||
*/
|
||||
if (vms->gic_version == 3) {
|
||||
if (vms->gic_version == VIRT_GIC_VERSION_3) {
|
||||
clustersz = GICV3_TARGETLIST_BITS;
|
||||
} else {
|
||||
clustersz = GIC_TARGETLIST_BITS;
|
||||
@ -1535,6 +1535,105 @@ static void virt_set_memmap(VirtMachineState *vms)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* finalize_gic_version - Determines the final gic_version
|
||||
* according to the gic-version property
|
||||
*
|
||||
* Default GIC type is v2
|
||||
*/
|
||||
static void finalize_gic_version(VirtMachineState *vms)
|
||||
{
|
||||
unsigned int max_cpus = MACHINE(vms)->smp.max_cpus;
|
||||
|
||||
if (kvm_enabled()) {
|
||||
int probe_bitmap;
|
||||
|
||||
if (!kvm_irqchip_in_kernel()) {
|
||||
switch (vms->gic_version) {
|
||||
case VIRT_GIC_VERSION_HOST:
|
||||
warn_report(
|
||||
"gic-version=host not relevant with kernel-irqchip=off "
|
||||
"as only userspace GICv2 is supported. Using v2 ...");
|
||||
return;
|
||||
case VIRT_GIC_VERSION_MAX:
|
||||
case VIRT_GIC_VERSION_NOSEL:
|
||||
vms->gic_version = VIRT_GIC_VERSION_2;
|
||||
return;
|
||||
case VIRT_GIC_VERSION_2:
|
||||
return;
|
||||
case VIRT_GIC_VERSION_3:
|
||||
error_report(
|
||||
"gic-version=3 is not supported with kernel-irqchip=off");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
probe_bitmap = kvm_arm_vgic_probe();
|
||||
if (!probe_bitmap) {
|
||||
error_report("Unable to determine GIC version supported by host");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
switch (vms->gic_version) {
|
||||
case VIRT_GIC_VERSION_HOST:
|
||||
case VIRT_GIC_VERSION_MAX:
|
||||
if (probe_bitmap & KVM_ARM_VGIC_V3) {
|
||||
vms->gic_version = VIRT_GIC_VERSION_3;
|
||||
} else {
|
||||
vms->gic_version = VIRT_GIC_VERSION_2;
|
||||
}
|
||||
return;
|
||||
case VIRT_GIC_VERSION_NOSEL:
|
||||
if ((probe_bitmap & KVM_ARM_VGIC_V2) && max_cpus <= GIC_NCPU) {
|
||||
vms->gic_version = VIRT_GIC_VERSION_2;
|
||||
} else if (probe_bitmap & KVM_ARM_VGIC_V3) {
|
||||
/*
|
||||
* in case the host does not support v2 in-kernel emulation or
|
||||
* the end-user requested more than 8 VCPUs we now default
|
||||
* to v3. In any case defaulting to v2 would be broken.
|
||||
*/
|
||||
vms->gic_version = VIRT_GIC_VERSION_3;
|
||||
} else if (max_cpus > GIC_NCPU) {
|
||||
error_report("host only supports in-kernel GICv2 emulation "
|
||||
"but more than 8 vcpus are requested");
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
case VIRT_GIC_VERSION_2:
|
||||
case VIRT_GIC_VERSION_3:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Check chosen version is effectively supported by the host */
|
||||
if (vms->gic_version == VIRT_GIC_VERSION_2 &&
|
||||
!(probe_bitmap & KVM_ARM_VGIC_V2)) {
|
||||
error_report("host does not support in-kernel GICv2 emulation");
|
||||
exit(1);
|
||||
} else if (vms->gic_version == VIRT_GIC_VERSION_3 &&
|
||||
!(probe_bitmap & KVM_ARM_VGIC_V3)) {
|
||||
error_report("host does not support in-kernel GICv3 emulation");
|
||||
exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* TCG mode */
|
||||
switch (vms->gic_version) {
|
||||
case VIRT_GIC_VERSION_NOSEL:
|
||||
vms->gic_version = VIRT_GIC_VERSION_2;
|
||||
break;
|
||||
case VIRT_GIC_VERSION_MAX:
|
||||
vms->gic_version = VIRT_GIC_VERSION_3;
|
||||
break;
|
||||
case VIRT_GIC_VERSION_HOST:
|
||||
error_report("gic-version=host requires KVM");
|
||||
exit(1);
|
||||
case VIRT_GIC_VERSION_2:
|
||||
case VIRT_GIC_VERSION_3:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void machvirt_init(MachineState *machine)
|
||||
{
|
||||
VirtMachineState *vms = VIRT_MACHINE(machine);
|
||||
@ -1561,25 +1660,7 @@ static void machvirt_init(MachineState *machine)
|
||||
/* We can probe only here because during property set
|
||||
* KVM is not available yet
|
||||
*/
|
||||
if (vms->gic_version <= 0) {
|
||||
/* "host" or "max" */
|
||||
if (!kvm_enabled()) {
|
||||
if (vms->gic_version == 0) {
|
||||
error_report("gic-version=host requires KVM");
|
||||
exit(1);
|
||||
} else {
|
||||
/* "max": currently means 3 for TCG */
|
||||
vms->gic_version = 3;
|
||||
}
|
||||
} else {
|
||||
vms->gic_version = kvm_arm_vgic_probe();
|
||||
if (!vms->gic_version) {
|
||||
error_report(
|
||||
"Unable to determine GIC version supported by host");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
finalize_gic_version(vms);
|
||||
|
||||
if (!cpu_type_valid(machine->cpu_type)) {
|
||||
error_report("mach-virt: CPU type %s not supported", machine->cpu_type);
|
||||
@ -1628,7 +1709,7 @@ static void machvirt_init(MachineState *machine)
|
||||
/* The maximum number of CPUs depends on the GIC version, or on how
|
||||
* many redistributors we can fit into the memory map.
|
||||
*/
|
||||
if (vms->gic_version == 3) {
|
||||
if (vms->gic_version == VIRT_GIC_VERSION_3) {
|
||||
virt_max_cpus =
|
||||
vms->memmap[VIRT_GIC_REDIST].size / GICV3_REDIST_SIZE;
|
||||
virt_max_cpus +=
|
||||
@ -1856,7 +1937,7 @@ static void virt_set_its(Object *obj, bool value, Error **errp)
|
||||
static char *virt_get_gic_version(Object *obj, Error **errp)
|
||||
{
|
||||
VirtMachineState *vms = VIRT_MACHINE(obj);
|
||||
const char *val = vms->gic_version == 3 ? "3" : "2";
|
||||
const char *val = vms->gic_version == VIRT_GIC_VERSION_3 ? "3" : "2";
|
||||
|
||||
return g_strdup(val);
|
||||
}
|
||||
@ -1866,13 +1947,13 @@ static void virt_set_gic_version(Object *obj, const char *value, Error **errp)
|
||||
VirtMachineState *vms = VIRT_MACHINE(obj);
|
||||
|
||||
if (!strcmp(value, "3")) {
|
||||
vms->gic_version = 3;
|
||||
vms->gic_version = VIRT_GIC_VERSION_3;
|
||||
} else if (!strcmp(value, "2")) {
|
||||
vms->gic_version = 2;
|
||||
vms->gic_version = VIRT_GIC_VERSION_2;
|
||||
} else if (!strcmp(value, "host")) {
|
||||
vms->gic_version = 0; /* Will probe later */
|
||||
vms->gic_version = VIRT_GIC_VERSION_HOST; /* Will probe later */
|
||||
} else if (!strcmp(value, "max")) {
|
||||
vms->gic_version = -1; /* Will probe later */
|
||||
vms->gic_version = VIRT_GIC_VERSION_MAX; /* Will probe later */
|
||||
} else {
|
||||
error_setg(errp, "Invalid gic-version value");
|
||||
error_append_hint(errp, "Valid values are 3, 2, host, max.\n");
|
||||
@ -2140,13 +2221,13 @@ static void virt_instance_init(Object *obj)
|
||||
"Set on/off to enable/disable using "
|
||||
"physical address space above 32 bits",
|
||||
NULL);
|
||||
/* Default GIC type is v2 */
|
||||
vms->gic_version = 2;
|
||||
vms->gic_version = VIRT_GIC_VERSION_NOSEL;
|
||||
object_property_add_str(obj, "gic-version", virt_get_gic_version,
|
||||
virt_set_gic_version, NULL);
|
||||
object_property_set_description(obj, "gic-version",
|
||||
"Set GIC version. "
|
||||
"Valid values are 2, 3 and host", NULL);
|
||||
"Valid values are 2, 3, host and max",
|
||||
NULL);
|
||||
|
||||
vms->highmem_ecam = !vmc->no_highmem_ecam;
|
||||
|
||||
|
@ -2593,6 +2593,12 @@ static void armv7m_nvic_reset(DeviceState *dev)
|
||||
s->itns[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We updated state that affects the CPU's MMUidx and thus its hflags;
|
||||
* and we can't guarantee that we run before the CPU reset function.
|
||||
*/
|
||||
arm_rebuild_hflags(&s->cpu->env);
|
||||
}
|
||||
|
||||
static void nvic_systick_trigger(void *opaque, int n, int level)
|
||||
|
@ -28,6 +28,11 @@ common-obj-$(CONFIG_MACIO) += macio/
|
||||
|
||||
common-obj-$(CONFIG_IVSHMEM_DEVICE) += ivshmem.o
|
||||
|
||||
common-obj-$(CONFIG_ALLWINNER_H3) += allwinner-h3-ccu.o
|
||||
obj-$(CONFIG_ALLWINNER_H3) += allwinner-cpucfg.o
|
||||
common-obj-$(CONFIG_ALLWINNER_H3) += allwinner-h3-dramc.o
|
||||
common-obj-$(CONFIG_ALLWINNER_H3) += allwinner-h3-sysctrl.o
|
||||
common-obj-$(CONFIG_ALLWINNER_H3) += allwinner-sid.o
|
||||
common-obj-$(CONFIG_REALVIEW) += arm_sysctl.o
|
||||
common-obj-$(CONFIG_NSERIES) += cbus.o
|
||||
common-obj-$(CONFIG_ECCMEMCTL) += eccmemctl.o
|
||||
|
282
hw/misc/allwinner-cpucfg.c
Normal file
282
hw/misc/allwinner-cpucfg.c
Normal file
@ -0,0 +1,282 @@
|
||||
/*
|
||||
* Allwinner CPU Configuration Module emulation
|
||||
*
|
||||
* Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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 "qemu/error-report.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "hw/core/cpu.h"
|
||||
#include "target/arm/arm-powerctl.h"
|
||||
#include "target/arm/cpu.h"
|
||||
#include "hw/misc/allwinner-cpucfg.h"
|
||||
#include "trace.h"
|
||||
|
||||
/* CPUCFG register offsets */
|
||||
enum {
|
||||
REG_CPUS_RST_CTRL = 0x0000, /* CPUs Reset Control */
|
||||
REG_CPU0_RST_CTRL = 0x0040, /* CPU#0 Reset Control */
|
||||
REG_CPU0_CTRL = 0x0044, /* CPU#0 Control */
|
||||
REG_CPU0_STATUS = 0x0048, /* CPU#0 Status */
|
||||
REG_CPU1_RST_CTRL = 0x0080, /* CPU#1 Reset Control */
|
||||
REG_CPU1_CTRL = 0x0084, /* CPU#1 Control */
|
||||
REG_CPU1_STATUS = 0x0088, /* CPU#1 Status */
|
||||
REG_CPU2_RST_CTRL = 0x00C0, /* CPU#2 Reset Control */
|
||||
REG_CPU2_CTRL = 0x00C4, /* CPU#2 Control */
|
||||
REG_CPU2_STATUS = 0x00C8, /* CPU#2 Status */
|
||||
REG_CPU3_RST_CTRL = 0x0100, /* CPU#3 Reset Control */
|
||||
REG_CPU3_CTRL = 0x0104, /* CPU#3 Control */
|
||||
REG_CPU3_STATUS = 0x0108, /* CPU#3 Status */
|
||||
REG_CPU_SYS_RST = 0x0140, /* CPU System Reset */
|
||||
REG_CLK_GATING = 0x0144, /* CPU Clock Gating */
|
||||
REG_GEN_CTRL = 0x0184, /* General Control */
|
||||
REG_SUPER_STANDBY = 0x01A0, /* Super Standby Flag */
|
||||
REG_ENTRY_ADDR = 0x01A4, /* Reset Entry Address */
|
||||
REG_DBG_EXTERN = 0x01E4, /* Debug External */
|
||||
REG_CNT64_CTRL = 0x0280, /* 64-bit Counter Control */
|
||||
REG_CNT64_LOW = 0x0284, /* 64-bit Counter Low */
|
||||
REG_CNT64_HIGH = 0x0288, /* 64-bit Counter High */
|
||||
};
|
||||
|
||||
/* CPUCFG register flags */
|
||||
enum {
|
||||
CPUX_RESET_RELEASED = ((1 << 1) | (1 << 0)),
|
||||
CPUX_STATUS_SMP = (1 << 0),
|
||||
CPU_SYS_RESET_RELEASED = (1 << 0),
|
||||
CLK_GATING_ENABLE = ((1 << 8) | 0xF),
|
||||
};
|
||||
|
||||
/* CPUCFG register reset values */
|
||||
enum {
|
||||
REG_CLK_GATING_RST = 0x0000010F,
|
||||
REG_GEN_CTRL_RST = 0x00000020,
|
||||
REG_SUPER_STANDBY_RST = 0x0,
|
||||
REG_CNT64_CTRL_RST = 0x0,
|
||||
};
|
||||
|
||||
/* CPUCFG constants */
|
||||
enum {
|
||||
CPU_EXCEPTION_LEVEL_ON_RESET = 3, /* EL3 */
|
||||
};
|
||||
|
||||
static void allwinner_cpucfg_cpu_reset(AwCpuCfgState *s, uint8_t cpu_id)
|
||||
{
|
||||
int ret;
|
||||
|
||||
trace_allwinner_cpucfg_cpu_reset(cpu_id, s->entry_addr);
|
||||
|
||||
ARMCPU *target_cpu = ARM_CPU(arm_get_cpu_by_id(cpu_id));
|
||||
if (!target_cpu) {
|
||||
/*
|
||||
* Called with a bogus value for cpu_id. Guest error will
|
||||
* already have been logged, we can simply return here.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
bool target_aa64 = arm_feature(&target_cpu->env, ARM_FEATURE_AARCH64);
|
||||
|
||||
ret = arm_set_cpu_on(cpu_id, s->entry_addr, 0,
|
||||
CPU_EXCEPTION_LEVEL_ON_RESET, target_aa64);
|
||||
if (ret != QEMU_ARM_POWERCTL_RET_SUCCESS) {
|
||||
error_report("%s: failed to bring up CPU %d: err %d",
|
||||
__func__, cpu_id, ret);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t allwinner_cpucfg_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
const AwCpuCfgState *s = AW_CPUCFG(opaque);
|
||||
uint64_t val = 0;
|
||||
|
||||
switch (offset) {
|
||||
case REG_CPUS_RST_CTRL: /* CPUs Reset Control */
|
||||
case REG_CPU_SYS_RST: /* CPU System Reset */
|
||||
val = CPU_SYS_RESET_RELEASED;
|
||||
break;
|
||||
case REG_CPU0_RST_CTRL: /* CPU#0 Reset Control */
|
||||
case REG_CPU1_RST_CTRL: /* CPU#1 Reset Control */
|
||||
case REG_CPU2_RST_CTRL: /* CPU#2 Reset Control */
|
||||
case REG_CPU3_RST_CTRL: /* CPU#3 Reset Control */
|
||||
val = CPUX_RESET_RELEASED;
|
||||
break;
|
||||
case REG_CPU0_CTRL: /* CPU#0 Control */
|
||||
case REG_CPU1_CTRL: /* CPU#1 Control */
|
||||
case REG_CPU2_CTRL: /* CPU#2 Control */
|
||||
case REG_CPU3_CTRL: /* CPU#3 Control */
|
||||
val = 0;
|
||||
break;
|
||||
case REG_CPU0_STATUS: /* CPU#0 Status */
|
||||
case REG_CPU1_STATUS: /* CPU#1 Status */
|
||||
case REG_CPU2_STATUS: /* CPU#2 Status */
|
||||
case REG_CPU3_STATUS: /* CPU#3 Status */
|
||||
val = CPUX_STATUS_SMP;
|
||||
break;
|
||||
case REG_CLK_GATING: /* CPU Clock Gating */
|
||||
val = CLK_GATING_ENABLE;
|
||||
break;
|
||||
case REG_GEN_CTRL: /* General Control */
|
||||
val = s->gen_ctrl;
|
||||
break;
|
||||
case REG_SUPER_STANDBY: /* Super Standby Flag */
|
||||
val = s->super_standby;
|
||||
break;
|
||||
case REG_ENTRY_ADDR: /* Reset Entry Address */
|
||||
val = s->entry_addr;
|
||||
break;
|
||||
case REG_DBG_EXTERN: /* Debug External */
|
||||
case REG_CNT64_CTRL: /* 64-bit Counter Control */
|
||||
case REG_CNT64_LOW: /* 64-bit Counter Low */
|
||||
case REG_CNT64_HIGH: /* 64-bit Counter High */
|
||||
qemu_log_mask(LOG_UNIMP, "%s: unimplemented register at 0x%04x\n",
|
||||
__func__, (uint32_t)offset);
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
|
||||
__func__, (uint32_t)offset);
|
||||
break;
|
||||
}
|
||||
|
||||
trace_allwinner_cpucfg_read(offset, val, size);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static void allwinner_cpucfg_write(void *opaque, hwaddr offset,
|
||||
uint64_t val, unsigned size)
|
||||
{
|
||||
AwCpuCfgState *s = AW_CPUCFG(opaque);
|
||||
|
||||
trace_allwinner_cpucfg_write(offset, val, size);
|
||||
|
||||
switch (offset) {
|
||||
case REG_CPUS_RST_CTRL: /* CPUs Reset Control */
|
||||
case REG_CPU_SYS_RST: /* CPU System Reset */
|
||||
break;
|
||||
case REG_CPU0_RST_CTRL: /* CPU#0 Reset Control */
|
||||
case REG_CPU1_RST_CTRL: /* CPU#1 Reset Control */
|
||||
case REG_CPU2_RST_CTRL: /* CPU#2 Reset Control */
|
||||
case REG_CPU3_RST_CTRL: /* CPU#3 Reset Control */
|
||||
if (val) {
|
||||
allwinner_cpucfg_cpu_reset(s, (offset - REG_CPU0_RST_CTRL) >> 6);
|
||||
}
|
||||
break;
|
||||
case REG_CPU0_CTRL: /* CPU#0 Control */
|
||||
case REG_CPU1_CTRL: /* CPU#1 Control */
|
||||
case REG_CPU2_CTRL: /* CPU#2 Control */
|
||||
case REG_CPU3_CTRL: /* CPU#3 Control */
|
||||
case REG_CPU0_STATUS: /* CPU#0 Status */
|
||||
case REG_CPU1_STATUS: /* CPU#1 Status */
|
||||
case REG_CPU2_STATUS: /* CPU#2 Status */
|
||||
case REG_CPU3_STATUS: /* CPU#3 Status */
|
||||
case REG_CLK_GATING: /* CPU Clock Gating */
|
||||
break;
|
||||
case REG_GEN_CTRL: /* General Control */
|
||||
s->gen_ctrl = val;
|
||||
break;
|
||||
case REG_SUPER_STANDBY: /* Super Standby Flag */
|
||||
s->super_standby = val;
|
||||
break;
|
||||
case REG_ENTRY_ADDR: /* Reset Entry Address */
|
||||
s->entry_addr = val;
|
||||
break;
|
||||
case REG_DBG_EXTERN: /* Debug External */
|
||||
case REG_CNT64_CTRL: /* 64-bit Counter Control */
|
||||
case REG_CNT64_LOW: /* 64-bit Counter Low */
|
||||
case REG_CNT64_HIGH: /* 64-bit Counter High */
|
||||
qemu_log_mask(LOG_UNIMP, "%s: unimplemented register at 0x%04x\n",
|
||||
__func__, (uint32_t)offset);
|
||||
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_cpucfg_ops = {
|
||||
.read = allwinner_cpucfg_read,
|
||||
.write = allwinner_cpucfg_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
.impl.min_access_size = 4,
|
||||
};
|
||||
|
||||
static void allwinner_cpucfg_reset(DeviceState *dev)
|
||||
{
|
||||
AwCpuCfgState *s = AW_CPUCFG(dev);
|
||||
|
||||
/* Set default values for registers */
|
||||
s->gen_ctrl = REG_GEN_CTRL_RST;
|
||||
s->super_standby = REG_SUPER_STANDBY_RST;
|
||||
s->entry_addr = 0;
|
||||
}
|
||||
|
||||
static void allwinner_cpucfg_init(Object *obj)
|
||||
{
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
AwCpuCfgState *s = AW_CPUCFG(obj);
|
||||
|
||||
/* Memory mapping */
|
||||
memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_cpucfg_ops, s,
|
||||
TYPE_AW_CPUCFG, 1 * KiB);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
}
|
||||
|
||||
static const VMStateDescription allwinner_cpucfg_vmstate = {
|
||||
.name = "allwinner-cpucfg",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32(gen_ctrl, AwCpuCfgState),
|
||||
VMSTATE_UINT32(super_standby, AwCpuCfgState),
|
||||
VMSTATE_UINT32(entry_addr, AwCpuCfgState),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void allwinner_cpucfg_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->reset = allwinner_cpucfg_reset;
|
||||
dc->vmsd = &allwinner_cpucfg_vmstate;
|
||||
}
|
||||
|
||||
static const TypeInfo allwinner_cpucfg_info = {
|
||||
.name = TYPE_AW_CPUCFG,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_init = allwinner_cpucfg_init,
|
||||
.instance_size = sizeof(AwCpuCfgState),
|
||||
.class_init = allwinner_cpucfg_class_init,
|
||||
};
|
||||
|
||||
static void allwinner_cpucfg_register(void)
|
||||
{
|
||||
type_register_static(&allwinner_cpucfg_info);
|
||||
}
|
||||
|
||||
type_init(allwinner_cpucfg_register)
|
242
hw/misc/allwinner-h3-ccu.c
Normal file
242
hw/misc/allwinner-h3-ccu.c
Normal file
@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Allwinner H3 Clock Control Unit emulation
|
||||
*
|
||||
* Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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-h3-ccu.h"
|
||||
|
||||
/* CCU register offsets */
|
||||
enum {
|
||||
REG_PLL_CPUX = 0x0000, /* PLL CPUX Control */
|
||||
REG_PLL_AUDIO = 0x0008, /* PLL Audio Control */
|
||||
REG_PLL_VIDEO = 0x0010, /* PLL Video Control */
|
||||
REG_PLL_VE = 0x0018, /* PLL VE Control */
|
||||
REG_PLL_DDR = 0x0020, /* PLL DDR Control */
|
||||
REG_PLL_PERIPH0 = 0x0028, /* PLL Peripherals 0 Control */
|
||||
REG_PLL_GPU = 0x0038, /* PLL GPU Control */
|
||||
REG_PLL_PERIPH1 = 0x0044, /* PLL Peripherals 1 Control */
|
||||
REG_PLL_DE = 0x0048, /* PLL Display Engine Control */
|
||||
REG_CPUX_AXI = 0x0050, /* CPUX/AXI Configuration */
|
||||
REG_APB1 = 0x0054, /* ARM Peripheral Bus 1 Config */
|
||||
REG_APB2 = 0x0058, /* ARM Peripheral Bus 2 Config */
|
||||
REG_DRAM_CFG = 0x00F4, /* DRAM Configuration */
|
||||
REG_MBUS = 0x00FC, /* MBUS Reset */
|
||||
REG_PLL_TIME0 = 0x0200, /* PLL Stable Time 0 */
|
||||
REG_PLL_TIME1 = 0x0204, /* PLL Stable Time 1 */
|
||||
REG_PLL_CPUX_BIAS = 0x0220, /* PLL CPUX Bias */
|
||||
REG_PLL_AUDIO_BIAS = 0x0224, /* PLL Audio Bias */
|
||||
REG_PLL_VIDEO_BIAS = 0x0228, /* PLL Video Bias */
|
||||
REG_PLL_VE_BIAS = 0x022C, /* PLL VE Bias */
|
||||
REG_PLL_DDR_BIAS = 0x0230, /* PLL DDR Bias */
|
||||
REG_PLL_PERIPH0_BIAS = 0x0234, /* PLL Peripherals 0 Bias */
|
||||
REG_PLL_GPU_BIAS = 0x023C, /* PLL GPU Bias */
|
||||
REG_PLL_PERIPH1_BIAS = 0x0244, /* PLL Peripherals 1 Bias */
|
||||
REG_PLL_DE_BIAS = 0x0248, /* PLL Display Engine Bias */
|
||||
REG_PLL_CPUX_TUNING = 0x0250, /* PLL CPUX Tuning */
|
||||
REG_PLL_DDR_TUNING = 0x0260, /* PLL DDR Tuning */
|
||||
};
|
||||
|
||||
#define REG_INDEX(offset) (offset / sizeof(uint32_t))
|
||||
|
||||
/* CCU register flags */
|
||||
enum {
|
||||
REG_DRAM_CFG_UPDATE = (1 << 16),
|
||||
};
|
||||
|
||||
enum {
|
||||
REG_PLL_ENABLE = (1 << 31),
|
||||
REG_PLL_LOCK = (1 << 28),
|
||||
};
|
||||
|
||||
|
||||
/* CCU register reset values */
|
||||
enum {
|
||||
REG_PLL_CPUX_RST = 0x00001000,
|
||||
REG_PLL_AUDIO_RST = 0x00035514,
|
||||
REG_PLL_VIDEO_RST = 0x03006207,
|
||||
REG_PLL_VE_RST = 0x03006207,
|
||||
REG_PLL_DDR_RST = 0x00001000,
|
||||
REG_PLL_PERIPH0_RST = 0x00041811,
|
||||
REG_PLL_GPU_RST = 0x03006207,
|
||||
REG_PLL_PERIPH1_RST = 0x00041811,
|
||||
REG_PLL_DE_RST = 0x03006207,
|
||||
REG_CPUX_AXI_RST = 0x00010000,
|
||||
REG_APB1_RST = 0x00001010,
|
||||
REG_APB2_RST = 0x01000000,
|
||||
REG_DRAM_CFG_RST = 0x00000000,
|
||||
REG_MBUS_RST = 0x80000000,
|
||||
REG_PLL_TIME0_RST = 0x000000FF,
|
||||
REG_PLL_TIME1_RST = 0x000000FF,
|
||||
REG_PLL_CPUX_BIAS_RST = 0x08100200,
|
||||
REG_PLL_AUDIO_BIAS_RST = 0x10100000,
|
||||
REG_PLL_VIDEO_BIAS_RST = 0x10100000,
|
||||
REG_PLL_VE_BIAS_RST = 0x10100000,
|
||||
REG_PLL_DDR_BIAS_RST = 0x81104000,
|
||||
REG_PLL_PERIPH0_BIAS_RST = 0x10100010,
|
||||
REG_PLL_GPU_BIAS_RST = 0x10100000,
|
||||
REG_PLL_PERIPH1_BIAS_RST = 0x10100010,
|
||||
REG_PLL_DE_BIAS_RST = 0x10100000,
|
||||
REG_PLL_CPUX_TUNING_RST = 0x0A101000,
|
||||
REG_PLL_DDR_TUNING_RST = 0x14880000,
|
||||
};
|
||||
|
||||
static uint64_t allwinner_h3_ccu_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
const AwH3ClockCtlState *s = AW_H3_CCU(opaque);
|
||||
const uint32_t idx = REG_INDEX(offset);
|
||||
|
||||
switch (offset) {
|
||||
case 0x308 ... AW_H3_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_h3_ccu_write(void *opaque, hwaddr offset,
|
||||
uint64_t val, unsigned size)
|
||||
{
|
||||
AwH3ClockCtlState *s = AW_H3_CCU(opaque);
|
||||
const uint32_t idx = REG_INDEX(offset);
|
||||
|
||||
switch (offset) {
|
||||
case REG_DRAM_CFG: /* DRAM Configuration */
|
||||
val &= ~REG_DRAM_CFG_UPDATE;
|
||||
break;
|
||||
case REG_PLL_CPUX: /* PLL CPUX Control */
|
||||
case REG_PLL_AUDIO: /* PLL Audio Control */
|
||||
case REG_PLL_VIDEO: /* PLL Video Control */
|
||||
case REG_PLL_VE: /* PLL VE Control */
|
||||
case REG_PLL_DDR: /* PLL DDR Control */
|
||||
case REG_PLL_PERIPH0: /* PLL Peripherals 0 Control */
|
||||
case REG_PLL_GPU: /* PLL GPU Control */
|
||||
case REG_PLL_PERIPH1: /* PLL Peripherals 1 Control */
|
||||
case REG_PLL_DE: /* PLL Display Engine Control */
|
||||
if (val & REG_PLL_ENABLE) {
|
||||
val |= REG_PLL_LOCK;
|
||||
}
|
||||
break;
|
||||
case 0x308 ... AW_H3_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[idx] = (uint32_t) val;
|
||||
}
|
||||
|
||||
static const MemoryRegionOps allwinner_h3_ccu_ops = {
|
||||
.read = allwinner_h3_ccu_read,
|
||||
.write = allwinner_h3_ccu_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
.impl.min_access_size = 4,
|
||||
};
|
||||
|
||||
static void allwinner_h3_ccu_reset(DeviceState *dev)
|
||||
{
|
||||
AwH3ClockCtlState *s = AW_H3_CCU(dev);
|
||||
|
||||
/* Set default values for registers */
|
||||
s->regs[REG_INDEX(REG_PLL_CPUX)] = REG_PLL_CPUX_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_AUDIO)] = REG_PLL_AUDIO_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_VIDEO)] = REG_PLL_VIDEO_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_VE)] = REG_PLL_VE_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_DDR)] = REG_PLL_DDR_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_PERIPH0)] = REG_PLL_PERIPH0_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_GPU)] = REG_PLL_GPU_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_PERIPH1)] = REG_PLL_PERIPH1_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_DE)] = REG_PLL_DE_RST;
|
||||
s->regs[REG_INDEX(REG_CPUX_AXI)] = REG_CPUX_AXI_RST;
|
||||
s->regs[REG_INDEX(REG_APB1)] = REG_APB1_RST;
|
||||
s->regs[REG_INDEX(REG_APB2)] = REG_APB2_RST;
|
||||
s->regs[REG_INDEX(REG_DRAM_CFG)] = REG_DRAM_CFG_RST;
|
||||
s->regs[REG_INDEX(REG_MBUS)] = REG_MBUS_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_TIME0)] = REG_PLL_TIME0_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_TIME1)] = REG_PLL_TIME1_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_CPUX_BIAS)] = REG_PLL_CPUX_BIAS_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_AUDIO_BIAS)] = REG_PLL_AUDIO_BIAS_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_VIDEO_BIAS)] = REG_PLL_VIDEO_BIAS_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_VE_BIAS)] = REG_PLL_VE_BIAS_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_DDR_BIAS)] = REG_PLL_DDR_BIAS_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_PERIPH0_BIAS)] = REG_PLL_PERIPH0_BIAS_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_GPU_BIAS)] = REG_PLL_GPU_BIAS_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_PERIPH1_BIAS)] = REG_PLL_PERIPH1_BIAS_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_DE_BIAS)] = REG_PLL_DE_BIAS_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_CPUX_TUNING)] = REG_PLL_CPUX_TUNING_RST;
|
||||
s->regs[REG_INDEX(REG_PLL_DDR_TUNING)] = REG_PLL_DDR_TUNING_RST;
|
||||
}
|
||||
|
||||
static void allwinner_h3_ccu_init(Object *obj)
|
||||
{
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
AwH3ClockCtlState *s = AW_H3_CCU(obj);
|
||||
|
||||
/* Memory mapping */
|
||||
memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_h3_ccu_ops, s,
|
||||
TYPE_AW_H3_CCU, AW_H3_CCU_IOSIZE);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
}
|
||||
|
||||
static const VMStateDescription allwinner_h3_ccu_vmstate = {
|
||||
.name = "allwinner-h3-ccu",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32_ARRAY(regs, AwH3ClockCtlState, AW_H3_CCU_REGS_NUM),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void allwinner_h3_ccu_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->reset = allwinner_h3_ccu_reset;
|
||||
dc->vmsd = &allwinner_h3_ccu_vmstate;
|
||||
}
|
||||
|
||||
static const TypeInfo allwinner_h3_ccu_info = {
|
||||
.name = TYPE_AW_H3_CCU,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_init = allwinner_h3_ccu_init,
|
||||
.instance_size = sizeof(AwH3ClockCtlState),
|
||||
.class_init = allwinner_h3_ccu_class_init,
|
||||
};
|
||||
|
||||
static void allwinner_h3_ccu_register(void)
|
||||
{
|
||||
type_register_static(&allwinner_h3_ccu_info);
|
||||
}
|
||||
|
||||
type_init(allwinner_h3_ccu_register)
|
358
hw/misc/allwinner-h3-dramc.c
Normal file
358
hw/misc/allwinner-h3-dramc.c
Normal file
@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Allwinner H3 SDRAM Controller emulation
|
||||
*
|
||||
* Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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 "hw/misc/allwinner-h3-dramc.h"
|
||||
#include "trace.h"
|
||||
|
||||
#define REG_INDEX(offset) (offset / sizeof(uint32_t))
|
||||
|
||||
/* DRAMCOM register offsets */
|
||||
enum {
|
||||
REG_DRAMCOM_CR = 0x0000, /* Control Register */
|
||||
};
|
||||
|
||||
/* 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 */
|
||||
};
|
||||
|
||||
/* DRAMCTL register flags */
|
||||
enum {
|
||||
REG_DRAMCTL_PGSR_INITDONE = (1 << 0),
|
||||
};
|
||||
|
||||
enum {
|
||||
REG_DRAMCTL_STATR_ACTIVE = (1 << 0),
|
||||
};
|
||||
|
||||
static void allwinner_h3_dramc_map_rows(AwH3DramCtlState *s, uint8_t row_bits,
|
||||
uint8_t bank_bits, uint16_t page_size)
|
||||
{
|
||||
/*
|
||||
* This function simulates row addressing behavior when bootloader
|
||||
* software attempts to detect the amount of available SDRAM. In U-Boot
|
||||
* the controller is configured with the widest row addressing available.
|
||||
* Then a pattern is written to RAM at an offset on the row boundary size.
|
||||
* If the value read back equals the value read back from the
|
||||
* start of RAM, the bootloader knows the amount of row bits.
|
||||
*
|
||||
* This function inserts a mirrored memory region when the configured row
|
||||
* bits are not matching the actual emulated memory, to simulate the
|
||||
* same behavior on hardware as expected by the bootloader.
|
||||
*/
|
||||
uint8_t row_bits_actual = 0;
|
||||
|
||||
/* Calculate the actual row bits using the ram_size property */
|
||||
for (uint8_t i = 8; i < 12; i++) {
|
||||
if (1 << i == s->ram_size) {
|
||||
row_bits_actual = i + 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (s->ram_size == (1 << (row_bits - 3))) {
|
||||
/* When row bits is the expected value, remove the mirror */
|
||||
memory_region_set_enabled(&s->row_mirror_alias, false);
|
||||
trace_allwinner_h3_dramc_rowmirror_disable();
|
||||
|
||||
} else if (row_bits_actual) {
|
||||
/* Row bits not matching ram_size, install the rows mirror */
|
||||
hwaddr row_mirror = s->ram_addr + ((1 << (row_bits_actual +
|
||||
bank_bits)) * page_size);
|
||||
|
||||
memory_region_set_enabled(&s->row_mirror_alias, true);
|
||||
memory_region_set_address(&s->row_mirror_alias, row_mirror);
|
||||
|
||||
trace_allwinner_h3_dramc_rowmirror_enable(row_mirror);
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t allwinner_h3_dramcom_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
const AwH3DramCtlState *s = AW_H3_DRAMC(opaque);
|
||||
const uint32_t idx = REG_INDEX(offset);
|
||||
|
||||
if (idx >= AW_H3_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_h3_dramcom_read(offset, s->dramcom[idx], size);
|
||||
|
||||
return s->dramcom[idx];
|
||||
}
|
||||
|
||||
static void allwinner_h3_dramcom_write(void *opaque, hwaddr offset,
|
||||
uint64_t val, unsigned size)
|
||||
{
|
||||
AwH3DramCtlState *s = AW_H3_DRAMC(opaque);
|
||||
const uint32_t idx = REG_INDEX(offset);
|
||||
|
||||
trace_allwinner_h3_dramcom_write(offset, val, size);
|
||||
|
||||
if (idx >= AW_H3_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 */
|
||||
allwinner_h3_dramc_map_rows(s, ((val >> 4) & 0xf) + 1,
|
||||
((val >> 2) & 0x1) + 2,
|
||||
1 << (((val >> 8) & 0xf) + 3));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
|
||||
s->dramcom[idx] = (uint32_t) val;
|
||||
}
|
||||
|
||||
static uint64_t allwinner_h3_dramctl_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
const AwH3DramCtlState *s = AW_H3_DRAMC(opaque);
|
||||
const uint32_t idx = REG_INDEX(offset);
|
||||
|
||||
if (idx >= AW_H3_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_h3_dramctl_read(offset, s->dramctl[idx], size);
|
||||
|
||||
return s->dramctl[idx];
|
||||
}
|
||||
|
||||
static void allwinner_h3_dramctl_write(void *opaque, hwaddr offset,
|
||||
uint64_t val, unsigned size)
|
||||
{
|
||||
AwH3DramCtlState *s = AW_H3_DRAMC(opaque);
|
||||
const uint32_t idx = REG_INDEX(offset);
|
||||
|
||||
trace_allwinner_h3_dramctl_write(offset, val, size);
|
||||
|
||||
if (idx >= AW_H3_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;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
s->dramctl[idx] = (uint32_t) val;
|
||||
}
|
||||
|
||||
static uint64_t allwinner_h3_dramphy_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
const AwH3DramCtlState *s = AW_H3_DRAMC(opaque);
|
||||
const uint32_t idx = REG_INDEX(offset);
|
||||
|
||||
if (idx >= AW_H3_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_h3_dramphy_read(offset, s->dramphy[idx], size);
|
||||
|
||||
return s->dramphy[idx];
|
||||
}
|
||||
|
||||
static void allwinner_h3_dramphy_write(void *opaque, hwaddr offset,
|
||||
uint64_t val, unsigned size)
|
||||
{
|
||||
AwH3DramCtlState *s = AW_H3_DRAMC(opaque);
|
||||
const uint32_t idx = REG_INDEX(offset);
|
||||
|
||||
trace_allwinner_h3_dramphy_write(offset, val, size);
|
||||
|
||||
if (idx >= AW_H3_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_h3_dramcom_ops = {
|
||||
.read = allwinner_h3_dramcom_read,
|
||||
.write = allwinner_h3_dramcom_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
.impl.min_access_size = 4,
|
||||
};
|
||||
|
||||
static const MemoryRegionOps allwinner_h3_dramctl_ops = {
|
||||
.read = allwinner_h3_dramctl_read,
|
||||
.write = allwinner_h3_dramctl_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
.impl.min_access_size = 4,
|
||||
};
|
||||
|
||||
static const MemoryRegionOps allwinner_h3_dramphy_ops = {
|
||||
.read = allwinner_h3_dramphy_read,
|
||||
.write = allwinner_h3_dramphy_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
.impl.min_access_size = 4,
|
||||
};
|
||||
|
||||
static void allwinner_h3_dramc_reset(DeviceState *dev)
|
||||
{
|
||||
AwH3DramCtlState *s = AW_H3_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_h3_dramc_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
AwH3DramCtlState *s = AW_H3_DRAMC(dev);
|
||||
|
||||
/* Only power of 2 RAM sizes from 256MiB up to 2048MiB are supported */
|
||||
for (uint8_t i = 8; i < 13; i++) {
|
||||
if (1 << i == s->ram_size) {
|
||||
break;
|
||||
} else if (i == 12) {
|
||||
error_report("%s: ram-size %u MiB is not supported",
|
||||
__func__, s->ram_size);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Setup row mirror mappings */
|
||||
memory_region_init_ram(&s->row_mirror, OBJECT(s),
|
||||
"allwinner-h3-dramc.row-mirror",
|
||||
4 * KiB, &error_abort);
|
||||
memory_region_add_subregion_overlap(get_system_memory(), s->ram_addr,
|
||||
&s->row_mirror, 10);
|
||||
|
||||
memory_region_init_alias(&s->row_mirror_alias, OBJECT(s),
|
||||
"allwinner-h3-dramc.row-mirror-alias",
|
||||
&s->row_mirror, 0, 4 * KiB);
|
||||
memory_region_add_subregion_overlap(get_system_memory(),
|
||||
s->ram_addr + 1 * MiB,
|
||||
&s->row_mirror_alias, 10);
|
||||
memory_region_set_enabled(&s->row_mirror_alias, false);
|
||||
}
|
||||
|
||||
static void allwinner_h3_dramc_init(Object *obj)
|
||||
{
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
AwH3DramCtlState *s = AW_H3_DRAMC(obj);
|
||||
|
||||
/* DRAMCOM registers */
|
||||
memory_region_init_io(&s->dramcom_iomem, OBJECT(s),
|
||||
&allwinner_h3_dramcom_ops, s,
|
||||
TYPE_AW_H3_DRAMC, 4 * KiB);
|
||||
sysbus_init_mmio(sbd, &s->dramcom_iomem);
|
||||
|
||||
/* DRAMCTL registers */
|
||||
memory_region_init_io(&s->dramctl_iomem, OBJECT(s),
|
||||
&allwinner_h3_dramctl_ops, s,
|
||||
TYPE_AW_H3_DRAMC, 4 * KiB);
|
||||
sysbus_init_mmio(sbd, &s->dramctl_iomem);
|
||||
|
||||
/* DRAMPHY registers */
|
||||
memory_region_init_io(&s->dramphy_iomem, OBJECT(s),
|
||||
&allwinner_h3_dramphy_ops, s,
|
||||
TYPE_AW_H3_DRAMC, 4 * KiB);
|
||||
sysbus_init_mmio(sbd, &s->dramphy_iomem);
|
||||
}
|
||||
|
||||
static Property allwinner_h3_dramc_properties[] = {
|
||||
DEFINE_PROP_UINT64("ram-addr", AwH3DramCtlState, ram_addr, 0x0),
|
||||
DEFINE_PROP_UINT32("ram-size", AwH3DramCtlState, ram_size, 256 * MiB),
|
||||
DEFINE_PROP_END_OF_LIST()
|
||||
};
|
||||
|
||||
static const VMStateDescription allwinner_h3_dramc_vmstate = {
|
||||
.name = "allwinner-h3-dramc",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32_ARRAY(dramcom, AwH3DramCtlState, AW_H3_DRAMCOM_REGS_NUM),
|
||||
VMSTATE_UINT32_ARRAY(dramctl, AwH3DramCtlState, AW_H3_DRAMCTL_REGS_NUM),
|
||||
VMSTATE_UINT32_ARRAY(dramphy, AwH3DramCtlState, AW_H3_DRAMPHY_REGS_NUM),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void allwinner_h3_dramc_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->reset = allwinner_h3_dramc_reset;
|
||||
dc->vmsd = &allwinner_h3_dramc_vmstate;
|
||||
dc->realize = allwinner_h3_dramc_realize;
|
||||
device_class_set_props(dc, allwinner_h3_dramc_properties);
|
||||
}
|
||||
|
||||
static const TypeInfo allwinner_h3_dramc_info = {
|
||||
.name = TYPE_AW_H3_DRAMC,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_init = allwinner_h3_dramc_init,
|
||||
.instance_size = sizeof(AwH3DramCtlState),
|
||||
.class_init = allwinner_h3_dramc_class_init,
|
||||
};
|
||||
|
||||
static void allwinner_h3_dramc_register(void)
|
||||
{
|
||||
type_register_static(&allwinner_h3_dramc_info);
|
||||
}
|
||||
|
||||
type_init(allwinner_h3_dramc_register)
|
140
hw/misc/allwinner-h3-sysctrl.c
Normal file
140
hw/misc/allwinner-h3-sysctrl.c
Normal file
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Allwinner H3 System Control emulation
|
||||
*
|
||||
* Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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-h3-sysctrl.h"
|
||||
|
||||
/* System Control register offsets */
|
||||
enum {
|
||||
REG_VER = 0x24, /* Version */
|
||||
REG_EMAC_PHY_CLK = 0x30, /* EMAC PHY Clock */
|
||||
};
|
||||
|
||||
#define REG_INDEX(offset) (offset / sizeof(uint32_t))
|
||||
|
||||
/* System Control register reset values */
|
||||
enum {
|
||||
REG_VER_RST = 0x0,
|
||||
REG_EMAC_PHY_CLK_RST = 0x58000,
|
||||
};
|
||||
|
||||
static uint64_t allwinner_h3_sysctrl_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
const AwH3SysCtrlState *s = AW_H3_SYSCTRL(opaque);
|
||||
const uint32_t idx = REG_INDEX(offset);
|
||||
|
||||
if (idx >= AW_H3_SYSCTRL_REGS_NUM) {
|
||||
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_h3_sysctrl_write(void *opaque, hwaddr offset,
|
||||
uint64_t val, unsigned size)
|
||||
{
|
||||
AwH3SysCtrlState *s = AW_H3_SYSCTRL(opaque);
|
||||
const uint32_t idx = REG_INDEX(offset);
|
||||
|
||||
if (idx >= AW_H3_SYSCTRL_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_VER: /* Version */
|
||||
break;
|
||||
default:
|
||||
s->regs[idx] = (uint32_t) val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps allwinner_h3_sysctrl_ops = {
|
||||
.read = allwinner_h3_sysctrl_read,
|
||||
.write = allwinner_h3_sysctrl_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
.impl.min_access_size = 4,
|
||||
};
|
||||
|
||||
static void allwinner_h3_sysctrl_reset(DeviceState *dev)
|
||||
{
|
||||
AwH3SysCtrlState *s = AW_H3_SYSCTRL(dev);
|
||||
|
||||
/* Set default values for registers */
|
||||
s->regs[REG_INDEX(REG_VER)] = REG_VER_RST;
|
||||
s->regs[REG_INDEX(REG_EMAC_PHY_CLK)] = REG_EMAC_PHY_CLK_RST;
|
||||
}
|
||||
|
||||
static void allwinner_h3_sysctrl_init(Object *obj)
|
||||
{
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
AwH3SysCtrlState *s = AW_H3_SYSCTRL(obj);
|
||||
|
||||
/* Memory mapping */
|
||||
memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_h3_sysctrl_ops, s,
|
||||
TYPE_AW_H3_SYSCTRL, 4 * KiB);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
}
|
||||
|
||||
static const VMStateDescription allwinner_h3_sysctrl_vmstate = {
|
||||
.name = "allwinner-h3-sysctrl",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32_ARRAY(regs, AwH3SysCtrlState, AW_H3_SYSCTRL_REGS_NUM),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void allwinner_h3_sysctrl_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->reset = allwinner_h3_sysctrl_reset;
|
||||
dc->vmsd = &allwinner_h3_sysctrl_vmstate;
|
||||
}
|
||||
|
||||
static const TypeInfo allwinner_h3_sysctrl_info = {
|
||||
.name = TYPE_AW_H3_SYSCTRL,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_init = allwinner_h3_sysctrl_init,
|
||||
.instance_size = sizeof(AwH3SysCtrlState),
|
||||
.class_init = allwinner_h3_sysctrl_class_init,
|
||||
};
|
||||
|
||||
static void allwinner_h3_sysctrl_register(void)
|
||||
{
|
||||
type_register_static(&allwinner_h3_sysctrl_info);
|
||||
}
|
||||
|
||||
type_init(allwinner_h3_sysctrl_register)
|
168
hw/misc/allwinner-sid.c
Normal file
168
hw/misc/allwinner-sid.c
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Allwinner Security ID emulation
|
||||
*
|
||||
* Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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 "qemu/guest-random.h"
|
||||
#include "qapi/error.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "hw/misc/allwinner-sid.h"
|
||||
#include "trace.h"
|
||||
|
||||
/* SID register offsets */
|
||||
enum {
|
||||
REG_PRCTL = 0x40, /* Control */
|
||||
REG_RDKEY = 0x60, /* Read Key */
|
||||
};
|
||||
|
||||
/* SID register flags */
|
||||
enum {
|
||||
REG_PRCTL_WRITE = 0x0002, /* Unknown write flag */
|
||||
REG_PRCTL_OP_LOCK = 0xAC00, /* Lock operation */
|
||||
};
|
||||
|
||||
static uint64_t allwinner_sid_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
const AwSidState *s = AW_SID(opaque);
|
||||
uint64_t val = 0;
|
||||
|
||||
switch (offset) {
|
||||
case REG_PRCTL: /* Control */
|
||||
val = s->control;
|
||||
break;
|
||||
case REG_RDKEY: /* Read Key */
|
||||
val = s->rdkey;
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
|
||||
__func__, (uint32_t)offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
trace_allwinner_sid_read(offset, val, size);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static void allwinner_sid_write(void *opaque, hwaddr offset,
|
||||
uint64_t val, unsigned size)
|
||||
{
|
||||
AwSidState *s = AW_SID(opaque);
|
||||
|
||||
trace_allwinner_sid_write(offset, val, size);
|
||||
|
||||
switch (offset) {
|
||||
case REG_PRCTL: /* Control */
|
||||
s->control = val;
|
||||
|
||||
if ((s->control & REG_PRCTL_OP_LOCK) &&
|
||||
(s->control & REG_PRCTL_WRITE)) {
|
||||
uint32_t id = s->control >> 16;
|
||||
|
||||
if (id <= sizeof(QemuUUID) - sizeof(s->rdkey)) {
|
||||
s->rdkey = ldl_be_p(&s->identifier.data[id]);
|
||||
}
|
||||
}
|
||||
s->control &= ~REG_PRCTL_WRITE;
|
||||
break;
|
||||
case REG_RDKEY: /* Read Key */
|
||||
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_sid_ops = {
|
||||
.read = allwinner_sid_read,
|
||||
.write = allwinner_sid_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
.impl.min_access_size = 4,
|
||||
};
|
||||
|
||||
static void allwinner_sid_reset(DeviceState *dev)
|
||||
{
|
||||
AwSidState *s = AW_SID(dev);
|
||||
|
||||
/* Set default values for registers */
|
||||
s->control = 0;
|
||||
s->rdkey = 0;
|
||||
}
|
||||
|
||||
static void allwinner_sid_init(Object *obj)
|
||||
{
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
AwSidState *s = AW_SID(obj);
|
||||
|
||||
/* Memory mapping */
|
||||
memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_sid_ops, s,
|
||||
TYPE_AW_SID, 1 * KiB);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
}
|
||||
|
||||
static Property allwinner_sid_properties[] = {
|
||||
DEFINE_PROP_UUID_NODEFAULT("identifier", AwSidState, identifier),
|
||||
DEFINE_PROP_END_OF_LIST()
|
||||
};
|
||||
|
||||
static const VMStateDescription allwinner_sid_vmstate = {
|
||||
.name = "allwinner-sid",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32(control, AwSidState),
|
||||
VMSTATE_UINT32(rdkey, AwSidState),
|
||||
VMSTATE_UINT8_ARRAY_V(identifier.data, AwSidState, sizeof(QemuUUID), 1),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void allwinner_sid_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->reset = allwinner_sid_reset;
|
||||
dc->vmsd = &allwinner_sid_vmstate;
|
||||
device_class_set_props(dc, allwinner_sid_properties);
|
||||
}
|
||||
|
||||
static const TypeInfo allwinner_sid_info = {
|
||||
.name = TYPE_AW_SID,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_init = allwinner_sid_init,
|
||||
.instance_size = sizeof(AwSidState),
|
||||
.class_init = allwinner_sid_class_init,
|
||||
};
|
||||
|
||||
static void allwinner_sid_register(void)
|
||||
{
|
||||
type_register_static(&allwinner_sid_info);
|
||||
}
|
||||
|
||||
type_init(allwinner_sid_register)
|
@ -1,5 +1,24 @@
|
||||
# See docs/devel/tracing.txt for syntax documentation.
|
||||
|
||||
# allwinner-cpucfg.c
|
||||
allwinner_cpucfg_cpu_reset(uint8_t cpu_id, uint32_t reset_addr) "id %u, reset_addr 0x%" PRIu32
|
||||
allwinner_cpucfg_read(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
|
||||
allwinner_cpucfg_write(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
|
||||
|
||||
# allwinner-h3-dramc.c
|
||||
allwinner_h3_dramc_rowmirror_disable(void) "Disable row mirror"
|
||||
allwinner_h3_dramc_rowmirror_enable(uint64_t addr) "Enable row mirror: addr 0x%" PRIx64
|
||||
allwinner_h3_dramcom_read(uint64_t offset, uint64_t data, unsigned size) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
|
||||
allwinner_h3_dramcom_write(uint64_t offset, uint64_t data, unsigned size) "Write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
|
||||
allwinner_h3_dramctl_read(uint64_t offset, uint64_t data, unsigned size) "Read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
|
||||
allwinner_h3_dramctl_write(uint64_t offset, uint64_t data, unsigned size) "Write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
|
||||
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-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
|
||||
|
||||
# eccmemctl.c
|
||||
ecc_mem_writel_mer(uint32_t val) "Write memory enable 0x%08x"
|
||||
ecc_mem_writel_mdr(uint32_t val) "Write memory delay 0x%08x"
|
||||
|
@ -79,6 +79,9 @@ config MIPSNET
|
||||
config ALLWINNER_EMAC
|
||||
bool
|
||||
|
||||
config ALLWINNER_SUN8I_EMAC
|
||||
bool
|
||||
|
||||
config IMX_FEC
|
||||
bool
|
||||
|
||||
|
@ -23,6 +23,7 @@ common-obj-$(CONFIG_XGMAC) += xgmac.o
|
||||
common-obj-$(CONFIG_MIPSNET) += mipsnet.o
|
||||
common-obj-$(CONFIG_XILINX_AXI) += xilinx_axienet.o
|
||||
common-obj-$(CONFIG_ALLWINNER_EMAC) += allwinner_emac.o
|
||||
common-obj-$(CONFIG_ALLWINNER_SUN8I_EMAC) += allwinner-sun8i-emac.o
|
||||
common-obj-$(CONFIG_IMX_FEC) += imx_fec.o
|
||||
|
||||
common-obj-$(CONFIG_CADENCE) += cadence_gem.o
|
||||
|
871
hw/net/allwinner-sun8i-emac.c
Normal file
871
hw/net/allwinner-sun8i-emac.c
Normal file
@ -0,0 +1,871 @@
|
||||
/*
|
||||
* Allwinner Sun8i Ethernet MAC emulation
|
||||
*
|
||||
* Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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 "net/net.h"
|
||||
#include "hw/irq.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "qemu/log.h"
|
||||
#include "trace.h"
|
||||
#include "net/checksum.h"
|
||||
#include "qemu/module.h"
|
||||
#include "exec/cpu-common.h"
|
||||
#include "hw/net/allwinner-sun8i-emac.h"
|
||||
|
||||
/* EMAC register offsets */
|
||||
enum {
|
||||
REG_BASIC_CTL_0 = 0x0000, /* Basic Control 0 */
|
||||
REG_BASIC_CTL_1 = 0x0004, /* Basic Control 1 */
|
||||
REG_INT_STA = 0x0008, /* Interrupt Status */
|
||||
REG_INT_EN = 0x000C, /* Interrupt Enable */
|
||||
REG_TX_CTL_0 = 0x0010, /* Transmit Control 0 */
|
||||
REG_TX_CTL_1 = 0x0014, /* Transmit Control 1 */
|
||||
REG_TX_FLOW_CTL = 0x001C, /* Transmit Flow Control */
|
||||
REG_TX_DMA_DESC_LIST = 0x0020, /* Transmit Descriptor List Address */
|
||||
REG_RX_CTL_0 = 0x0024, /* Receive Control 0 */
|
||||
REG_RX_CTL_1 = 0x0028, /* Receive Control 1 */
|
||||
REG_RX_DMA_DESC_LIST = 0x0034, /* Receive Descriptor List Address */
|
||||
REG_FRM_FLT = 0x0038, /* Receive Frame Filter */
|
||||
REG_RX_HASH_0 = 0x0040, /* Receive Hash Table 0 */
|
||||
REG_RX_HASH_1 = 0x0044, /* Receive Hash Table 1 */
|
||||
REG_MII_CMD = 0x0048, /* Management Interface Command */
|
||||
REG_MII_DATA = 0x004C, /* Management Interface Data */
|
||||
REG_ADDR_HIGH = 0x0050, /* MAC Address High */
|
||||
REG_ADDR_LOW = 0x0054, /* MAC Address Low */
|
||||
REG_TX_DMA_STA = 0x00B0, /* Transmit DMA Status */
|
||||
REG_TX_CUR_DESC = 0x00B4, /* Transmit Current Descriptor */
|
||||
REG_TX_CUR_BUF = 0x00B8, /* Transmit Current Buffer */
|
||||
REG_RX_DMA_STA = 0x00C0, /* Receive DMA Status */
|
||||
REG_RX_CUR_DESC = 0x00C4, /* Receive Current Descriptor */
|
||||
REG_RX_CUR_BUF = 0x00C8, /* Receive Current Buffer */
|
||||
REG_RGMII_STA = 0x00D0, /* RGMII Status */
|
||||
};
|
||||
|
||||
/* EMAC register flags */
|
||||
enum {
|
||||
BASIC_CTL0_100Mbps = (0b11 << 2),
|
||||
BASIC_CTL0_FD = (1 << 0),
|
||||
BASIC_CTL1_SOFTRST = (1 << 0),
|
||||
};
|
||||
|
||||
enum {
|
||||
INT_STA_RGMII_LINK = (1 << 16),
|
||||
INT_STA_RX_EARLY = (1 << 13),
|
||||
INT_STA_RX_OVERFLOW = (1 << 12),
|
||||
INT_STA_RX_TIMEOUT = (1 << 11),
|
||||
INT_STA_RX_DMA_STOP = (1 << 10),
|
||||
INT_STA_RX_BUF_UA = (1 << 9),
|
||||
INT_STA_RX = (1 << 8),
|
||||
INT_STA_TX_EARLY = (1 << 5),
|
||||
INT_STA_TX_UNDERFLOW = (1 << 4),
|
||||
INT_STA_TX_TIMEOUT = (1 << 3),
|
||||
INT_STA_TX_BUF_UA = (1 << 2),
|
||||
INT_STA_TX_DMA_STOP = (1 << 1),
|
||||
INT_STA_TX = (1 << 0),
|
||||
};
|
||||
|
||||
enum {
|
||||
INT_EN_RX_EARLY = (1 << 13),
|
||||
INT_EN_RX_OVERFLOW = (1 << 12),
|
||||
INT_EN_RX_TIMEOUT = (1 << 11),
|
||||
INT_EN_RX_DMA_STOP = (1 << 10),
|
||||
INT_EN_RX_BUF_UA = (1 << 9),
|
||||
INT_EN_RX = (1 << 8),
|
||||
INT_EN_TX_EARLY = (1 << 5),
|
||||
INT_EN_TX_UNDERFLOW = (1 << 4),
|
||||
INT_EN_TX_TIMEOUT = (1 << 3),
|
||||
INT_EN_TX_BUF_UA = (1 << 2),
|
||||
INT_EN_TX_DMA_STOP = (1 << 1),
|
||||
INT_EN_TX = (1 << 0),
|
||||
};
|
||||
|
||||
enum {
|
||||
TX_CTL0_TX_EN = (1 << 31),
|
||||
TX_CTL1_TX_DMA_START = (1 << 31),
|
||||
TX_CTL1_TX_DMA_EN = (1 << 30),
|
||||
TX_CTL1_TX_FLUSH = (1 << 0),
|
||||
};
|
||||
|
||||
enum {
|
||||
RX_CTL0_RX_EN = (1 << 31),
|
||||
RX_CTL0_STRIP_FCS = (1 << 28),
|
||||
RX_CTL0_CRC_IPV4 = (1 << 27),
|
||||
};
|
||||
|
||||
enum {
|
||||
RX_CTL1_RX_DMA_START = (1 << 31),
|
||||
RX_CTL1_RX_DMA_EN = (1 << 30),
|
||||
RX_CTL1_RX_MD = (1 << 1),
|
||||
};
|
||||
|
||||
enum {
|
||||
RX_FRM_FLT_DIS_ADDR = (1 << 31),
|
||||
};
|
||||
|
||||
enum {
|
||||
MII_CMD_PHY_ADDR_SHIFT = (12),
|
||||
MII_CMD_PHY_ADDR_MASK = (0xf000),
|
||||
MII_CMD_PHY_REG_SHIFT = (4),
|
||||
MII_CMD_PHY_REG_MASK = (0xf0),
|
||||
MII_CMD_PHY_RW = (1 << 1),
|
||||
MII_CMD_PHY_BUSY = (1 << 0),
|
||||
};
|
||||
|
||||
enum {
|
||||
TX_DMA_STA_STOP = (0b000),
|
||||
TX_DMA_STA_RUN_FETCH = (0b001),
|
||||
TX_DMA_STA_WAIT_STA = (0b010),
|
||||
};
|
||||
|
||||
enum {
|
||||
RX_DMA_STA_STOP = (0b000),
|
||||
RX_DMA_STA_RUN_FETCH = (0b001),
|
||||
RX_DMA_STA_WAIT_FRM = (0b011),
|
||||
};
|
||||
|
||||
/* EMAC register reset values */
|
||||
enum {
|
||||
REG_BASIC_CTL_1_RST = 0x08000000,
|
||||
};
|
||||
|
||||
/* EMAC constants */
|
||||
enum {
|
||||
AW_SUN8I_EMAC_MIN_PKT_SZ = 64
|
||||
};
|
||||
|
||||
/* Transmit/receive frame descriptor */
|
||||
typedef struct FrameDescriptor {
|
||||
uint32_t status;
|
||||
uint32_t status2;
|
||||
uint32_t addr;
|
||||
uint32_t next;
|
||||
} FrameDescriptor;
|
||||
|
||||
/* Frame descriptor flags */
|
||||
enum {
|
||||
DESC_STATUS_CTL = (1 << 31),
|
||||
DESC_STATUS2_BUF_SIZE_MASK = (0x7ff),
|
||||
};
|
||||
|
||||
/* Transmit frame descriptor flags */
|
||||
enum {
|
||||
TX_DESC_STATUS_LENGTH_ERR = (1 << 14),
|
||||
TX_DESC_STATUS2_FIRST_DESC = (1 << 29),
|
||||
TX_DESC_STATUS2_LAST_DESC = (1 << 30),
|
||||
TX_DESC_STATUS2_CHECKSUM_MASK = (0x3 << 27),
|
||||
};
|
||||
|
||||
/* Receive frame descriptor flags */
|
||||
enum {
|
||||
RX_DESC_STATUS_FIRST_DESC = (1 << 9),
|
||||
RX_DESC_STATUS_LAST_DESC = (1 << 8),
|
||||
RX_DESC_STATUS_FRM_LEN_MASK = (0x3fff0000),
|
||||
RX_DESC_STATUS_FRM_LEN_SHIFT = (16),
|
||||
RX_DESC_STATUS_NO_BUF = (1 << 14),
|
||||
RX_DESC_STATUS_HEADER_ERR = (1 << 7),
|
||||
RX_DESC_STATUS_LENGTH_ERR = (1 << 4),
|
||||
RX_DESC_STATUS_CRC_ERR = (1 << 1),
|
||||
RX_DESC_STATUS_PAYLOAD_ERR = (1 << 0),
|
||||
RX_DESC_STATUS2_RX_INT_CTL = (1 << 31),
|
||||
};
|
||||
|
||||
/* MII register offsets */
|
||||
enum {
|
||||
MII_REG_CR = (0x0), /* Control */
|
||||
MII_REG_ST = (0x1), /* Status */
|
||||
MII_REG_ID_HIGH = (0x2), /* Identifier High */
|
||||
MII_REG_ID_LOW = (0x3), /* Identifier Low */
|
||||
MII_REG_ADV = (0x4), /* Advertised abilities */
|
||||
MII_REG_LPA = (0x5), /* Link partner abilities */
|
||||
};
|
||||
|
||||
/* MII register flags */
|
||||
enum {
|
||||
MII_REG_CR_RESET = (1 << 15),
|
||||
MII_REG_CR_POWERDOWN = (1 << 11),
|
||||
MII_REG_CR_10Mbit = (0),
|
||||
MII_REG_CR_100Mbit = (1 << 13),
|
||||
MII_REG_CR_1000Mbit = (1 << 6),
|
||||
MII_REG_CR_AUTO_NEG = (1 << 12),
|
||||
MII_REG_CR_AUTO_NEG_RESTART = (1 << 9),
|
||||
MII_REG_CR_FULLDUPLEX = (1 << 8),
|
||||
};
|
||||
|
||||
enum {
|
||||
MII_REG_ST_100BASE_T4 = (1 << 15),
|
||||
MII_REG_ST_100BASE_X_FD = (1 << 14),
|
||||
MII_REG_ST_100BASE_X_HD = (1 << 13),
|
||||
MII_REG_ST_10_FD = (1 << 12),
|
||||
MII_REG_ST_10_HD = (1 << 11),
|
||||
MII_REG_ST_100BASE_T2_FD = (1 << 10),
|
||||
MII_REG_ST_100BASE_T2_HD = (1 << 9),
|
||||
MII_REG_ST_AUTONEG_COMPLETE = (1 << 5),
|
||||
MII_REG_ST_AUTONEG_AVAIL = (1 << 3),
|
||||
MII_REG_ST_LINK_UP = (1 << 2),
|
||||
};
|
||||
|
||||
enum {
|
||||
MII_REG_LPA_10_HD = (1 << 5),
|
||||
MII_REG_LPA_10_FD = (1 << 6),
|
||||
MII_REG_LPA_100_HD = (1 << 7),
|
||||
MII_REG_LPA_100_FD = (1 << 8),
|
||||
MII_REG_LPA_PAUSE = (1 << 10),
|
||||
MII_REG_LPA_ASYMPAUSE = (1 << 11),
|
||||
};
|
||||
|
||||
/* MII constants */
|
||||
enum {
|
||||
MII_PHY_ID_HIGH = 0x0044,
|
||||
MII_PHY_ID_LOW = 0x1400,
|
||||
};
|
||||
|
||||
static void allwinner_sun8i_emac_mii_set_link(AwSun8iEmacState *s,
|
||||
bool link_active)
|
||||
{
|
||||
if (link_active) {
|
||||
s->mii_st |= MII_REG_ST_LINK_UP;
|
||||
} else {
|
||||
s->mii_st &= ~MII_REG_ST_LINK_UP;
|
||||
}
|
||||
}
|
||||
|
||||
static void allwinner_sun8i_emac_mii_reset(AwSun8iEmacState *s,
|
||||
bool link_active)
|
||||
{
|
||||
s->mii_cr = MII_REG_CR_100Mbit | MII_REG_CR_AUTO_NEG |
|
||||
MII_REG_CR_FULLDUPLEX;
|
||||
s->mii_st = MII_REG_ST_100BASE_T4 | MII_REG_ST_100BASE_X_FD |
|
||||
MII_REG_ST_100BASE_X_HD | MII_REG_ST_10_FD | MII_REG_ST_10_HD |
|
||||
MII_REG_ST_100BASE_T2_FD | MII_REG_ST_100BASE_T2_HD |
|
||||
MII_REG_ST_AUTONEG_COMPLETE | MII_REG_ST_AUTONEG_AVAIL;
|
||||
s->mii_adv = 0;
|
||||
|
||||
allwinner_sun8i_emac_mii_set_link(s, link_active);
|
||||
}
|
||||
|
||||
static void allwinner_sun8i_emac_mii_cmd(AwSun8iEmacState *s)
|
||||
{
|
||||
uint8_t addr, reg;
|
||||
|
||||
addr = (s->mii_cmd & MII_CMD_PHY_ADDR_MASK) >> MII_CMD_PHY_ADDR_SHIFT;
|
||||
reg = (s->mii_cmd & MII_CMD_PHY_REG_MASK) >> MII_CMD_PHY_REG_SHIFT;
|
||||
|
||||
if (addr != s->mii_phy_addr) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Read or write a PHY register? */
|
||||
if (s->mii_cmd & MII_CMD_PHY_RW) {
|
||||
trace_allwinner_sun8i_emac_mii_write_reg(reg, s->mii_data);
|
||||
|
||||
switch (reg) {
|
||||
case MII_REG_CR:
|
||||
if (s->mii_data & MII_REG_CR_RESET) {
|
||||
allwinner_sun8i_emac_mii_reset(s, s->mii_st &
|
||||
MII_REG_ST_LINK_UP);
|
||||
} else {
|
||||
s->mii_cr = s->mii_data & ~(MII_REG_CR_RESET |
|
||||
MII_REG_CR_AUTO_NEG_RESTART);
|
||||
}
|
||||
break;
|
||||
case MII_REG_ADV:
|
||||
s->mii_adv = s->mii_data;
|
||||
break;
|
||||
case MII_REG_ID_HIGH:
|
||||
case MII_REG_ID_LOW:
|
||||
case MII_REG_LPA:
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_UNIMP, "allwinner-h3-emac: write access to "
|
||||
"unknown MII register 0x%x\n", reg);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (reg) {
|
||||
case MII_REG_CR:
|
||||
s->mii_data = s->mii_cr;
|
||||
break;
|
||||
case MII_REG_ST:
|
||||
s->mii_data = s->mii_st;
|
||||
break;
|
||||
case MII_REG_ID_HIGH:
|
||||
s->mii_data = MII_PHY_ID_HIGH;
|
||||
break;
|
||||
case MII_REG_ID_LOW:
|
||||
s->mii_data = MII_PHY_ID_LOW;
|
||||
break;
|
||||
case MII_REG_ADV:
|
||||
s->mii_data = s->mii_adv;
|
||||
break;
|
||||
case MII_REG_LPA:
|
||||
s->mii_data = MII_REG_LPA_10_HD | MII_REG_LPA_10_FD |
|
||||
MII_REG_LPA_100_HD | MII_REG_LPA_100_FD |
|
||||
MII_REG_LPA_PAUSE | MII_REG_LPA_ASYMPAUSE;
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_UNIMP, "allwinner-h3-emac: read access to "
|
||||
"unknown MII register 0x%x\n", reg);
|
||||
s->mii_data = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
trace_allwinner_sun8i_emac_mii_read_reg(reg, s->mii_data);
|
||||
}
|
||||
}
|
||||
|
||||
static void allwinner_sun8i_emac_update_irq(AwSun8iEmacState *s)
|
||||
{
|
||||
qemu_set_irq(s->irq, (s->int_sta & s->int_en) != 0);
|
||||
}
|
||||
|
||||
static uint32_t allwinner_sun8i_emac_next_desc(FrameDescriptor *desc,
|
||||
size_t min_size)
|
||||
{
|
||||
uint32_t paddr = desc->next;
|
||||
|
||||
cpu_physical_memory_read(paddr, desc, sizeof(*desc));
|
||||
|
||||
if ((desc->status & DESC_STATUS_CTL) &&
|
||||
(desc->status2 & DESC_STATUS2_BUF_SIZE_MASK) >= min_size) {
|
||||
return paddr;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t allwinner_sun8i_emac_get_desc(FrameDescriptor *desc,
|
||||
uint32_t start_addr,
|
||||
size_t min_size)
|
||||
{
|
||||
uint32_t desc_addr = start_addr;
|
||||
|
||||
/* Note that the list is a cycle. Last entry points back to the head. */
|
||||
while (desc_addr != 0) {
|
||||
cpu_physical_memory_read(desc_addr, desc, sizeof(*desc));
|
||||
|
||||
if ((desc->status & DESC_STATUS_CTL) &&
|
||||
(desc->status2 & DESC_STATUS2_BUF_SIZE_MASK) >= min_size) {
|
||||
return desc_addr;
|
||||
} else if (desc->next == start_addr) {
|
||||
break;
|
||||
} else {
|
||||
desc_addr = desc->next;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t allwinner_sun8i_emac_rx_desc(AwSun8iEmacState *s,
|
||||
FrameDescriptor *desc,
|
||||
size_t min_size)
|
||||
{
|
||||
return allwinner_sun8i_emac_get_desc(desc, s->rx_desc_curr, min_size);
|
||||
}
|
||||
|
||||
static uint32_t allwinner_sun8i_emac_tx_desc(AwSun8iEmacState *s,
|
||||
FrameDescriptor *desc,
|
||||
size_t min_size)
|
||||
{
|
||||
return allwinner_sun8i_emac_get_desc(desc, s->tx_desc_head, min_size);
|
||||
}
|
||||
|
||||
static void allwinner_sun8i_emac_flush_desc(FrameDescriptor *desc,
|
||||
uint32_t phys_addr)
|
||||
{
|
||||
cpu_physical_memory_write(phys_addr, desc, sizeof(*desc));
|
||||
}
|
||||
|
||||
static int allwinner_sun8i_emac_can_receive(NetClientState *nc)
|
||||
{
|
||||
AwSun8iEmacState *s = qemu_get_nic_opaque(nc);
|
||||
FrameDescriptor desc;
|
||||
|
||||
return (s->rx_ctl0 & RX_CTL0_RX_EN) &&
|
||||
(allwinner_sun8i_emac_rx_desc(s, &desc, 0) != 0);
|
||||
}
|
||||
|
||||
static ssize_t allwinner_sun8i_emac_receive(NetClientState *nc,
|
||||
const uint8_t *buf,
|
||||
size_t size)
|
||||
{
|
||||
AwSun8iEmacState *s = qemu_get_nic_opaque(nc);
|
||||
FrameDescriptor desc;
|
||||
size_t bytes_left = size;
|
||||
size_t desc_bytes = 0;
|
||||
size_t pad_fcs_size = 4;
|
||||
size_t padding = 0;
|
||||
|
||||
if (!(s->rx_ctl0 & RX_CTL0_RX_EN)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
s->rx_desc_curr = allwinner_sun8i_emac_rx_desc(s, &desc,
|
||||
AW_SUN8I_EMAC_MIN_PKT_SZ);
|
||||
if (!s->rx_desc_curr) {
|
||||
s->int_sta |= INT_STA_RX_BUF_UA;
|
||||
}
|
||||
|
||||
/* Keep filling RX descriptors until the whole frame is written */
|
||||
while (s->rx_desc_curr && bytes_left > 0) {
|
||||
desc.status &= ~DESC_STATUS_CTL;
|
||||
desc.status &= ~RX_DESC_STATUS_FRM_LEN_MASK;
|
||||
|
||||
if (bytes_left == size) {
|
||||
desc.status |= RX_DESC_STATUS_FIRST_DESC;
|
||||
}
|
||||
|
||||
if ((desc.status2 & DESC_STATUS2_BUF_SIZE_MASK) <
|
||||
(bytes_left + pad_fcs_size)) {
|
||||
desc_bytes = desc.status2 & DESC_STATUS2_BUF_SIZE_MASK;
|
||||
desc.status |= desc_bytes << RX_DESC_STATUS_FRM_LEN_SHIFT;
|
||||
} else {
|
||||
padding = pad_fcs_size;
|
||||
if (bytes_left < AW_SUN8I_EMAC_MIN_PKT_SZ) {
|
||||
padding += (AW_SUN8I_EMAC_MIN_PKT_SZ - bytes_left);
|
||||
}
|
||||
|
||||
desc_bytes = (bytes_left);
|
||||
desc.status |= RX_DESC_STATUS_LAST_DESC;
|
||||
desc.status |= (bytes_left + padding)
|
||||
<< RX_DESC_STATUS_FRM_LEN_SHIFT;
|
||||
}
|
||||
|
||||
cpu_physical_memory_write(desc.addr, buf, desc_bytes);
|
||||
allwinner_sun8i_emac_flush_desc(&desc, s->rx_desc_curr);
|
||||
trace_allwinner_sun8i_emac_receive(s->rx_desc_curr, desc.addr,
|
||||
desc_bytes);
|
||||
|
||||
/* Check if frame needs to raise the receive interrupt */
|
||||
if (!(desc.status2 & RX_DESC_STATUS2_RX_INT_CTL)) {
|
||||
s->int_sta |= INT_STA_RX;
|
||||
}
|
||||
|
||||
/* Increment variables */
|
||||
buf += desc_bytes;
|
||||
bytes_left -= desc_bytes;
|
||||
|
||||
/* Move to the next descriptor */
|
||||
s->rx_desc_curr = allwinner_sun8i_emac_next_desc(&desc, 64);
|
||||
if (!s->rx_desc_curr) {
|
||||
/* Not enough buffer space available */
|
||||
s->int_sta |= INT_STA_RX_BUF_UA;
|
||||
s->rx_desc_curr = s->rx_desc_head;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Report receive DMA is finished */
|
||||
s->rx_ctl1 &= ~RX_CTL1_RX_DMA_START;
|
||||
allwinner_sun8i_emac_update_irq(s);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static void allwinner_sun8i_emac_transmit(AwSun8iEmacState *s)
|
||||
{
|
||||
NetClientState *nc = qemu_get_queue(s->nic);
|
||||
FrameDescriptor desc;
|
||||
size_t bytes = 0;
|
||||
size_t packet_bytes = 0;
|
||||
size_t transmitted = 0;
|
||||
static uint8_t packet_buf[2048];
|
||||
|
||||
s->tx_desc_curr = allwinner_sun8i_emac_tx_desc(s, &desc, 0);
|
||||
|
||||
/* Read all transmit descriptors */
|
||||
while (s->tx_desc_curr != 0) {
|
||||
|
||||
/* Read from physical memory into packet buffer */
|
||||
bytes = desc.status2 & DESC_STATUS2_BUF_SIZE_MASK;
|
||||
if (bytes + packet_bytes > sizeof(packet_buf)) {
|
||||
desc.status |= TX_DESC_STATUS_LENGTH_ERR;
|
||||
break;
|
||||
}
|
||||
cpu_physical_memory_read(desc.addr, packet_buf + packet_bytes, bytes);
|
||||
packet_bytes += bytes;
|
||||
desc.status &= ~DESC_STATUS_CTL;
|
||||
allwinner_sun8i_emac_flush_desc(&desc, s->tx_desc_curr);
|
||||
|
||||
/* After the last descriptor, send the packet */
|
||||
if (desc.status2 & TX_DESC_STATUS2_LAST_DESC) {
|
||||
if (desc.status2 & TX_DESC_STATUS2_CHECKSUM_MASK) {
|
||||
net_checksum_calculate(packet_buf, packet_bytes);
|
||||
}
|
||||
|
||||
qemu_send_packet(nc, packet_buf, packet_bytes);
|
||||
trace_allwinner_sun8i_emac_transmit(s->tx_desc_curr, desc.addr,
|
||||
bytes);
|
||||
|
||||
packet_bytes = 0;
|
||||
transmitted++;
|
||||
}
|
||||
s->tx_desc_curr = allwinner_sun8i_emac_next_desc(&desc, 0);
|
||||
}
|
||||
|
||||
/* Raise transmit completed interrupt */
|
||||
if (transmitted > 0) {
|
||||
s->int_sta |= INT_STA_TX;
|
||||
s->tx_ctl1 &= ~TX_CTL1_TX_DMA_START;
|
||||
allwinner_sun8i_emac_update_irq(s);
|
||||
}
|
||||
}
|
||||
|
||||
static void allwinner_sun8i_emac_reset(DeviceState *dev)
|
||||
{
|
||||
AwSun8iEmacState *s = AW_SUN8I_EMAC(dev);
|
||||
NetClientState *nc = qemu_get_queue(s->nic);
|
||||
|
||||
trace_allwinner_sun8i_emac_reset();
|
||||
|
||||
s->mii_cmd = 0;
|
||||
s->mii_data = 0;
|
||||
s->basic_ctl0 = 0;
|
||||
s->basic_ctl1 = REG_BASIC_CTL_1_RST;
|
||||
s->int_en = 0;
|
||||
s->int_sta = 0;
|
||||
s->frm_flt = 0;
|
||||
s->rx_ctl0 = 0;
|
||||
s->rx_ctl1 = RX_CTL1_RX_MD;
|
||||
s->rx_desc_head = 0;
|
||||
s->rx_desc_curr = 0;
|
||||
s->tx_ctl0 = 0;
|
||||
s->tx_ctl1 = 0;
|
||||
s->tx_desc_head = 0;
|
||||
s->tx_desc_curr = 0;
|
||||
s->tx_flowctl = 0;
|
||||
|
||||
allwinner_sun8i_emac_mii_reset(s, !nc->link_down);
|
||||
}
|
||||
|
||||
static uint64_t allwinner_sun8i_emac_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
AwSun8iEmacState *s = AW_SUN8I_EMAC(opaque);
|
||||
uint64_t value = 0;
|
||||
FrameDescriptor desc;
|
||||
|
||||
switch (offset) {
|
||||
case REG_BASIC_CTL_0: /* Basic Control 0 */
|
||||
value = s->basic_ctl0;
|
||||
break;
|
||||
case REG_BASIC_CTL_1: /* Basic Control 1 */
|
||||
value = s->basic_ctl1;
|
||||
break;
|
||||
case REG_INT_STA: /* Interrupt Status */
|
||||
value = s->int_sta;
|
||||
break;
|
||||
case REG_INT_EN: /* Interupt Enable */
|
||||
value = s->int_en;
|
||||
break;
|
||||
case REG_TX_CTL_0: /* Transmit Control 0 */
|
||||
value = s->tx_ctl0;
|
||||
break;
|
||||
case REG_TX_CTL_1: /* Transmit Control 1 */
|
||||
value = s->tx_ctl1;
|
||||
break;
|
||||
case REG_TX_FLOW_CTL: /* Transmit Flow Control */
|
||||
value = s->tx_flowctl;
|
||||
break;
|
||||
case REG_TX_DMA_DESC_LIST: /* Transmit Descriptor List Address */
|
||||
value = s->tx_desc_head;
|
||||
break;
|
||||
case REG_RX_CTL_0: /* Receive Control 0 */
|
||||
value = s->rx_ctl0;
|
||||
break;
|
||||
case REG_RX_CTL_1: /* Receive Control 1 */
|
||||
value = s->rx_ctl1;
|
||||
break;
|
||||
case REG_RX_DMA_DESC_LIST: /* Receive Descriptor List Address */
|
||||
value = s->rx_desc_head;
|
||||
break;
|
||||
case REG_FRM_FLT: /* Receive Frame Filter */
|
||||
value = s->frm_flt;
|
||||
break;
|
||||
case REG_RX_HASH_0: /* Receive Hash Table 0 */
|
||||
case REG_RX_HASH_1: /* Receive Hash Table 1 */
|
||||
break;
|
||||
case REG_MII_CMD: /* Management Interface Command */
|
||||
value = s->mii_cmd;
|
||||
break;
|
||||
case REG_MII_DATA: /* Management Interface Data */
|
||||
value = s->mii_data;
|
||||
break;
|
||||
case REG_ADDR_HIGH: /* MAC Address High */
|
||||
value = *(((uint32_t *) (s->conf.macaddr.a)) + 1);
|
||||
break;
|
||||
case REG_ADDR_LOW: /* MAC Address Low */
|
||||
value = *(uint32_t *) (s->conf.macaddr.a);
|
||||
break;
|
||||
case REG_TX_DMA_STA: /* Transmit DMA Status */
|
||||
break;
|
||||
case REG_TX_CUR_DESC: /* Transmit Current Descriptor */
|
||||
value = s->tx_desc_curr;
|
||||
break;
|
||||
case REG_TX_CUR_BUF: /* Transmit Current Buffer */
|
||||
if (s->tx_desc_curr != 0) {
|
||||
cpu_physical_memory_read(s->tx_desc_curr, &desc, sizeof(desc));
|
||||
value = desc.addr;
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_RX_DMA_STA: /* Receive DMA Status */
|
||||
break;
|
||||
case REG_RX_CUR_DESC: /* Receive Current Descriptor */
|
||||
value = s->rx_desc_curr;
|
||||
break;
|
||||
case REG_RX_CUR_BUF: /* Receive Current Buffer */
|
||||
if (s->rx_desc_curr != 0) {
|
||||
cpu_physical_memory_read(s->rx_desc_curr, &desc, sizeof(desc));
|
||||
value = desc.addr;
|
||||
} else {
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
case REG_RGMII_STA: /* RGMII Status */
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_UNIMP, "allwinner-h3-emac: read access to unknown "
|
||||
"EMAC register 0x" TARGET_FMT_plx "\n",
|
||||
offset);
|
||||
}
|
||||
|
||||
trace_allwinner_sun8i_emac_read(offset, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
static void allwinner_sun8i_emac_write(void *opaque, hwaddr offset,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
AwSun8iEmacState *s = AW_SUN8I_EMAC(opaque);
|
||||
NetClientState *nc = qemu_get_queue(s->nic);
|
||||
|
||||
trace_allwinner_sun8i_emac_write(offset, value);
|
||||
|
||||
switch (offset) {
|
||||
case REG_BASIC_CTL_0: /* Basic Control 0 */
|
||||
s->basic_ctl0 = value;
|
||||
break;
|
||||
case REG_BASIC_CTL_1: /* Basic Control 1 */
|
||||
if (value & BASIC_CTL1_SOFTRST) {
|
||||
allwinner_sun8i_emac_reset(DEVICE(s));
|
||||
value &= ~BASIC_CTL1_SOFTRST;
|
||||
}
|
||||
s->basic_ctl1 = value;
|
||||
if (allwinner_sun8i_emac_can_receive(nc)) {
|
||||
qemu_flush_queued_packets(nc);
|
||||
}
|
||||
break;
|
||||
case REG_INT_STA: /* Interrupt Status */
|
||||
s->int_sta &= ~value;
|
||||
allwinner_sun8i_emac_update_irq(s);
|
||||
break;
|
||||
case REG_INT_EN: /* Interrupt Enable */
|
||||
s->int_en = value;
|
||||
allwinner_sun8i_emac_update_irq(s);
|
||||
break;
|
||||
case REG_TX_CTL_0: /* Transmit Control 0 */
|
||||
s->tx_ctl0 = value;
|
||||
break;
|
||||
case REG_TX_CTL_1: /* Transmit Control 1 */
|
||||
s->tx_ctl1 = value;
|
||||
if (value & TX_CTL1_TX_DMA_EN) {
|
||||
allwinner_sun8i_emac_transmit(s);
|
||||
}
|
||||
break;
|
||||
case REG_TX_FLOW_CTL: /* Transmit Flow Control */
|
||||
s->tx_flowctl = value;
|
||||
break;
|
||||
case REG_TX_DMA_DESC_LIST: /* Transmit Descriptor List Address */
|
||||
s->tx_desc_head = value;
|
||||
s->tx_desc_curr = value;
|
||||
break;
|
||||
case REG_RX_CTL_0: /* Receive Control 0 */
|
||||
s->rx_ctl0 = value;
|
||||
break;
|
||||
case REG_RX_CTL_1: /* Receive Control 1 */
|
||||
s->rx_ctl1 = value | RX_CTL1_RX_MD;
|
||||
if ((value & RX_CTL1_RX_DMA_EN) &&
|
||||
allwinner_sun8i_emac_can_receive(nc)) {
|
||||
qemu_flush_queued_packets(nc);
|
||||
}
|
||||
break;
|
||||
case REG_RX_DMA_DESC_LIST: /* Receive Descriptor List Address */
|
||||
s->rx_desc_head = value;
|
||||
s->rx_desc_curr = value;
|
||||
break;
|
||||
case REG_FRM_FLT: /* Receive Frame Filter */
|
||||
s->frm_flt = value;
|
||||
break;
|
||||
case REG_RX_HASH_0: /* Receive Hash Table 0 */
|
||||
case REG_RX_HASH_1: /* Receive Hash Table 1 */
|
||||
break;
|
||||
case REG_MII_CMD: /* Management Interface Command */
|
||||
s->mii_cmd = value & ~MII_CMD_PHY_BUSY;
|
||||
allwinner_sun8i_emac_mii_cmd(s);
|
||||
break;
|
||||
case REG_MII_DATA: /* Management Interface Data */
|
||||
s->mii_data = value;
|
||||
break;
|
||||
case REG_ADDR_HIGH: /* MAC Address High */
|
||||
s->conf.macaddr.a[4] = (value & 0xff);
|
||||
s->conf.macaddr.a[5] = (value & 0xff00) >> 8;
|
||||
break;
|
||||
case REG_ADDR_LOW: /* MAC Address Low */
|
||||
s->conf.macaddr.a[0] = (value & 0xff);
|
||||
s->conf.macaddr.a[1] = (value & 0xff00) >> 8;
|
||||
s->conf.macaddr.a[2] = (value & 0xff0000) >> 16;
|
||||
s->conf.macaddr.a[3] = (value & 0xff000000) >> 24;
|
||||
break;
|
||||
case REG_TX_DMA_STA: /* Transmit DMA Status */
|
||||
case REG_TX_CUR_DESC: /* Transmit Current Descriptor */
|
||||
case REG_TX_CUR_BUF: /* Transmit Current Buffer */
|
||||
case REG_RX_DMA_STA: /* Receive DMA Status */
|
||||
case REG_RX_CUR_DESC: /* Receive Current Descriptor */
|
||||
case REG_RX_CUR_BUF: /* Receive Current Buffer */
|
||||
case REG_RGMII_STA: /* RGMII Status */
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_UNIMP, "allwinner-h3-emac: write access to unknown "
|
||||
"EMAC register 0x" TARGET_FMT_plx "\n",
|
||||
offset);
|
||||
}
|
||||
}
|
||||
|
||||
static void allwinner_sun8i_emac_set_link(NetClientState *nc)
|
||||
{
|
||||
AwSun8iEmacState *s = qemu_get_nic_opaque(nc);
|
||||
|
||||
trace_allwinner_sun8i_emac_set_link(!nc->link_down);
|
||||
allwinner_sun8i_emac_mii_set_link(s, !nc->link_down);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps allwinner_sun8i_emac_mem_ops = {
|
||||
.read = allwinner_sun8i_emac_read,
|
||||
.write = allwinner_sun8i_emac_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
.impl.min_access_size = 4,
|
||||
};
|
||||
|
||||
static NetClientInfo net_allwinner_sun8i_emac_info = {
|
||||
.type = NET_CLIENT_DRIVER_NIC,
|
||||
.size = sizeof(NICState),
|
||||
.can_receive = allwinner_sun8i_emac_can_receive,
|
||||
.receive = allwinner_sun8i_emac_receive,
|
||||
.link_status_changed = allwinner_sun8i_emac_set_link,
|
||||
};
|
||||
|
||||
static void allwinner_sun8i_emac_init(Object *obj)
|
||||
{
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
AwSun8iEmacState *s = AW_SUN8I_EMAC(obj);
|
||||
|
||||
memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_sun8i_emac_mem_ops,
|
||||
s, TYPE_AW_SUN8I_EMAC, 64 * KiB);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
sysbus_init_irq(sbd, &s->irq);
|
||||
}
|
||||
|
||||
static void allwinner_sun8i_emac_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
AwSun8iEmacState *s = AW_SUN8I_EMAC(dev);
|
||||
|
||||
qemu_macaddr_default_if_unset(&s->conf.macaddr);
|
||||
s->nic = qemu_new_nic(&net_allwinner_sun8i_emac_info, &s->conf,
|
||||
object_get_typename(OBJECT(dev)), dev->id, s);
|
||||
qemu_format_nic_info_str(qemu_get_queue(s->nic), s->conf.macaddr.a);
|
||||
}
|
||||
|
||||
static Property allwinner_sun8i_emac_properties[] = {
|
||||
DEFINE_NIC_PROPERTIES(AwSun8iEmacState, conf),
|
||||
DEFINE_PROP_UINT8("phy-addr", AwSun8iEmacState, mii_phy_addr, 0),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static int allwinner_sun8i_emac_post_load(void *opaque, int version_id)
|
||||
{
|
||||
AwSun8iEmacState *s = opaque;
|
||||
|
||||
allwinner_sun8i_emac_set_link(qemu_get_queue(s->nic));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_aw_emac = {
|
||||
.name = "allwinner-sun8i-emac",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.post_load = allwinner_sun8i_emac_post_load,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT8(mii_phy_addr, AwSun8iEmacState),
|
||||
VMSTATE_UINT32(mii_cmd, AwSun8iEmacState),
|
||||
VMSTATE_UINT32(mii_data, AwSun8iEmacState),
|
||||
VMSTATE_UINT32(mii_cr, AwSun8iEmacState),
|
||||
VMSTATE_UINT32(mii_st, AwSun8iEmacState),
|
||||
VMSTATE_UINT32(mii_adv, AwSun8iEmacState),
|
||||
VMSTATE_UINT32(basic_ctl0, AwSun8iEmacState),
|
||||
VMSTATE_UINT32(basic_ctl1, AwSun8iEmacState),
|
||||
VMSTATE_UINT32(int_en, AwSun8iEmacState),
|
||||
VMSTATE_UINT32(int_sta, AwSun8iEmacState),
|
||||
VMSTATE_UINT32(frm_flt, AwSun8iEmacState),
|
||||
VMSTATE_UINT32(rx_ctl0, AwSun8iEmacState),
|
||||
VMSTATE_UINT32(rx_ctl1, AwSun8iEmacState),
|
||||
VMSTATE_UINT32(rx_desc_head, AwSun8iEmacState),
|
||||
VMSTATE_UINT32(rx_desc_curr, AwSun8iEmacState),
|
||||
VMSTATE_UINT32(tx_ctl0, AwSun8iEmacState),
|
||||
VMSTATE_UINT32(tx_ctl1, AwSun8iEmacState),
|
||||
VMSTATE_UINT32(tx_desc_head, AwSun8iEmacState),
|
||||
VMSTATE_UINT32(tx_desc_curr, AwSun8iEmacState),
|
||||
VMSTATE_UINT32(tx_flowctl, AwSun8iEmacState),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void allwinner_sun8i_emac_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->realize = allwinner_sun8i_emac_realize;
|
||||
dc->reset = allwinner_sun8i_emac_reset;
|
||||
dc->vmsd = &vmstate_aw_emac;
|
||||
device_class_set_props(dc, allwinner_sun8i_emac_properties);
|
||||
}
|
||||
|
||||
static const TypeInfo allwinner_sun8i_emac_info = {
|
||||
.name = TYPE_AW_SUN8I_EMAC,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(AwSun8iEmacState),
|
||||
.instance_init = allwinner_sun8i_emac_init,
|
||||
.class_init = allwinner_sun8i_emac_class_init,
|
||||
};
|
||||
|
||||
static void allwinner_sun8i_emac_register_types(void)
|
||||
{
|
||||
type_register_static(&allwinner_sun8i_emac_info);
|
||||
}
|
||||
|
||||
type_init(allwinner_sun8i_emac_register_types)
|
@ -1,5 +1,15 @@
|
||||
# See docs/devel/tracing.txt for syntax documentation.
|
||||
|
||||
# allwinner-sun8i-emac.c
|
||||
allwinner_sun8i_emac_mii_write_reg(uint32_t reg, uint32_t value) "MII write: reg=0x%" PRIx32 " value=0x%" PRIx32
|
||||
allwinner_sun8i_emac_mii_read_reg(uint32_t reg, uint32_t value) "MII read: reg=0x%" PRIx32 " value=0x%" PRIx32
|
||||
allwinner_sun8i_emac_receive(uint32_t desc, uint32_t paddr, uint32_t bytes) "RX packet: desc=0x%" PRIx32 " paddr=0x%" PRIx32 " bytes=%" PRIu32
|
||||
allwinner_sun8i_emac_transmit(uint32_t desc, uint32_t paddr, uint32_t bytes) "TX packet: desc=0x%" PRIx32 " paddr=0x%" PRIx32 " bytes=%" PRIu32
|
||||
allwinner_sun8i_emac_reset(void) "HW reset"
|
||||
allwinner_sun8i_emac_set_link(bool active) "Set link: active=%u"
|
||||
allwinner_sun8i_emac_read(uint64_t offset, uint64_t val) "MMIO read: offset=0x%" PRIx64 " value=0x%" PRIx64
|
||||
allwinner_sun8i_emac_write(uint64_t offset, uint64_t val) "MMIO write: offset=0x%" PRIx64 " value=0x%" PRIx64
|
||||
|
||||
# etraxfs_eth.c
|
||||
mdio_phy_read(int regnum, uint16_t value) "read phy_reg:%d value:0x%04x"
|
||||
mdio_phy_write(int regnum, uint16_t value) "write phy_reg:%d value:0x%04x"
|
||||
|
@ -12,3 +12,4 @@ obj-$(CONFIG_MC146818RTC) += mc146818rtc.o
|
||||
common-obj-$(CONFIG_SUN4V_RTC) += sun4v-rtc.o
|
||||
common-obj-$(CONFIG_ASPEED_SOC) += aspeed_rtc.o
|
||||
common-obj-$(CONFIG_GOLDFISH_RTC) += goldfish_rtc.o
|
||||
common-obj-$(CONFIG_ALLWINNER_H3) += allwinner-rtc.o
|
||||
|
411
hw/rtc/allwinner-rtc.c
Normal file
411
hw/rtc/allwinner-rtc.c
Normal file
@ -0,0 +1,411 @@
|
||||
/*
|
||||
* Allwinner Real Time Clock emulation
|
||||
*
|
||||
* Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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 "qemu-common.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "hw/rtc/allwinner-rtc.h"
|
||||
#include "trace.h"
|
||||
|
||||
/* RTC registers */
|
||||
enum {
|
||||
REG_LOSC = 1, /* Low Oscillator Control */
|
||||
REG_YYMMDD, /* RTC Year-Month-Day */
|
||||
REG_HHMMSS, /* RTC Hour-Minute-Second */
|
||||
REG_ALARM1_WKHHMMSS, /* Alarm1 Week Hour-Minute-Second */
|
||||
REG_ALARM1_EN, /* Alarm1 Enable */
|
||||
REG_ALARM1_IRQ_EN, /* Alarm1 IRQ Enable */
|
||||
REG_ALARM1_IRQ_STA, /* Alarm1 IRQ Status */
|
||||
REG_GP0, /* General Purpose Register 0 */
|
||||
REG_GP1, /* General Purpose Register 1 */
|
||||
REG_GP2, /* General Purpose Register 2 */
|
||||
REG_GP3, /* General Purpose Register 3 */
|
||||
|
||||
/* sun4i registers */
|
||||
REG_ALARM1_DDHHMMSS, /* Alarm1 Day Hour-Minute-Second */
|
||||
REG_CPUCFG, /* CPU Configuration Register */
|
||||
|
||||
/* sun6i registers */
|
||||
REG_LOSC_AUTOSTA, /* LOSC Auto Switch Status */
|
||||
REG_INT_OSC_PRE, /* Internal OSC Clock Prescaler */
|
||||
REG_ALARM0_COUNTER, /* Alarm0 Counter */
|
||||
REG_ALARM0_CUR_VLU, /* Alarm0 Counter Current Value */
|
||||
REG_ALARM0_ENABLE, /* Alarm0 Enable */
|
||||
REG_ALARM0_IRQ_EN, /* Alarm0 IRQ Enable */
|
||||
REG_ALARM0_IRQ_STA, /* Alarm0 IRQ Status */
|
||||
REG_ALARM_CONFIG, /* Alarm Config */
|
||||
REG_LOSC_OUT_GATING, /* LOSC Output Gating Register */
|
||||
REG_GP4, /* General Purpose Register 4 */
|
||||
REG_GP5, /* General Purpose Register 5 */
|
||||
REG_GP6, /* General Purpose Register 6 */
|
||||
REG_GP7, /* General Purpose Register 7 */
|
||||
REG_RTC_DBG, /* RTC Debug Register */
|
||||
REG_GPL_HOLD_OUT, /* GPL Hold Output Register */
|
||||
REG_VDD_RTC, /* VDD RTC Regulate Register */
|
||||
REG_IC_CHARA, /* IC Characteristics Register */
|
||||
};
|
||||
|
||||
/* RTC register flags */
|
||||
enum {
|
||||
REG_LOSC_YMD = (1 << 7),
|
||||
REG_LOSC_HMS = (1 << 8),
|
||||
};
|
||||
|
||||
/* RTC sun4i register map (offset to name) */
|
||||
const uint8_t allwinner_rtc_sun4i_regmap[] = {
|
||||
[0x0000] = REG_LOSC,
|
||||
[0x0004] = REG_YYMMDD,
|
||||
[0x0008] = REG_HHMMSS,
|
||||
[0x000C] = REG_ALARM1_DDHHMMSS,
|
||||
[0x0010] = REG_ALARM1_WKHHMMSS,
|
||||
[0x0014] = REG_ALARM1_EN,
|
||||
[0x0018] = REG_ALARM1_IRQ_EN,
|
||||
[0x001C] = REG_ALARM1_IRQ_STA,
|
||||
[0x0020] = REG_GP0,
|
||||
[0x0024] = REG_GP1,
|
||||
[0x0028] = REG_GP2,
|
||||
[0x002C] = REG_GP3,
|
||||
[0x003C] = REG_CPUCFG,
|
||||
};
|
||||
|
||||
/* RTC sun6i register map (offset to name) */
|
||||
const uint8_t allwinner_rtc_sun6i_regmap[] = {
|
||||
[0x0000] = REG_LOSC,
|
||||
[0x0004] = REG_LOSC_AUTOSTA,
|
||||
[0x0008] = REG_INT_OSC_PRE,
|
||||
[0x0010] = REG_YYMMDD,
|
||||
[0x0014] = REG_HHMMSS,
|
||||
[0x0020] = REG_ALARM0_COUNTER,
|
||||
[0x0024] = REG_ALARM0_CUR_VLU,
|
||||
[0x0028] = REG_ALARM0_ENABLE,
|
||||
[0x002C] = REG_ALARM0_IRQ_EN,
|
||||
[0x0030] = REG_ALARM0_IRQ_STA,
|
||||
[0x0040] = REG_ALARM1_WKHHMMSS,
|
||||
[0x0044] = REG_ALARM1_EN,
|
||||
[0x0048] = REG_ALARM1_IRQ_EN,
|
||||
[0x004C] = REG_ALARM1_IRQ_STA,
|
||||
[0x0050] = REG_ALARM_CONFIG,
|
||||
[0x0060] = REG_LOSC_OUT_GATING,
|
||||
[0x0100] = REG_GP0,
|
||||
[0x0104] = REG_GP1,
|
||||
[0x0108] = REG_GP2,
|
||||
[0x010C] = REG_GP3,
|
||||
[0x0110] = REG_GP4,
|
||||
[0x0114] = REG_GP5,
|
||||
[0x0118] = REG_GP6,
|
||||
[0x011C] = REG_GP7,
|
||||
[0x0170] = REG_RTC_DBG,
|
||||
[0x0180] = REG_GPL_HOLD_OUT,
|
||||
[0x0190] = REG_VDD_RTC,
|
||||
[0x01F0] = REG_IC_CHARA,
|
||||
};
|
||||
|
||||
static bool allwinner_rtc_sun4i_read(AwRtcState *s, uint32_t offset)
|
||||
{
|
||||
/* no sun4i specific registers currently implemented */
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool allwinner_rtc_sun4i_write(AwRtcState *s, uint32_t offset,
|
||||
uint32_t data)
|
||||
{
|
||||
/* no sun4i specific registers currently implemented */
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool allwinner_rtc_sun6i_read(AwRtcState *s, uint32_t offset)
|
||||
{
|
||||
const AwRtcClass *c = AW_RTC_GET_CLASS(s);
|
||||
|
||||
switch (c->regmap[offset]) {
|
||||
case REG_GP4: /* General Purpose Register 4 */
|
||||
case REG_GP5: /* General Purpose Register 5 */
|
||||
case REG_GP6: /* General Purpose Register 6 */
|
||||
case REG_GP7: /* General Purpose Register 7 */
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool allwinner_rtc_sun6i_write(AwRtcState *s, uint32_t offset,
|
||||
uint32_t data)
|
||||
{
|
||||
const AwRtcClass *c = AW_RTC_GET_CLASS(s);
|
||||
|
||||
switch (c->regmap[offset]) {
|
||||
case REG_GP4: /* General Purpose Register 4 */
|
||||
case REG_GP5: /* General Purpose Register 5 */
|
||||
case REG_GP6: /* General Purpose Register 6 */
|
||||
case REG_GP7: /* General Purpose Register 7 */
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint64_t allwinner_rtc_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
AwRtcState *s = AW_RTC(opaque);
|
||||
const AwRtcClass *c = AW_RTC_GET_CLASS(s);
|
||||
uint64_t val = 0;
|
||||
|
||||
if (offset >= c->regmap_size) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
|
||||
__func__, (uint32_t)offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!c->regmap[offset]) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid register 0x%04x\n",
|
||||
__func__, (uint32_t)offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (c->regmap[offset]) {
|
||||
case REG_LOSC: /* Low Oscillator Control */
|
||||
val = s->regs[REG_LOSC];
|
||||
s->regs[REG_LOSC] &= ~(REG_LOSC_YMD | REG_LOSC_HMS);
|
||||
break;
|
||||
case REG_YYMMDD: /* RTC Year-Month-Day */
|
||||
case REG_HHMMSS: /* RTC Hour-Minute-Second */
|
||||
case REG_GP0: /* General Purpose Register 0 */
|
||||
case REG_GP1: /* General Purpose Register 1 */
|
||||
case REG_GP2: /* General Purpose Register 2 */
|
||||
case REG_GP3: /* General Purpose Register 3 */
|
||||
val = s->regs[c->regmap[offset]];
|
||||
break;
|
||||
default:
|
||||
if (!c->read(s, offset)) {
|
||||
qemu_log_mask(LOG_UNIMP, "%s: unimplemented register 0x%04x\n",
|
||||
__func__, (uint32_t)offset);
|
||||
}
|
||||
val = s->regs[c->regmap[offset]];
|
||||
break;
|
||||
}
|
||||
|
||||
trace_allwinner_rtc_read(offset, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
static void allwinner_rtc_write(void *opaque, hwaddr offset,
|
||||
uint64_t val, unsigned size)
|
||||
{
|
||||
AwRtcState *s = AW_RTC(opaque);
|
||||
const AwRtcClass *c = AW_RTC_GET_CLASS(s);
|
||||
|
||||
if (offset >= c->regmap_size) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
|
||||
__func__, (uint32_t)offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!c->regmap[offset]) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid register 0x%04x\n",
|
||||
__func__, (uint32_t)offset);
|
||||
return;
|
||||
}
|
||||
|
||||
trace_allwinner_rtc_write(offset, val);
|
||||
|
||||
switch (c->regmap[offset]) {
|
||||
case REG_YYMMDD: /* RTC Year-Month-Day */
|
||||
s->regs[REG_YYMMDD] = val;
|
||||
s->regs[REG_LOSC] |= REG_LOSC_YMD;
|
||||
break;
|
||||
case REG_HHMMSS: /* RTC Hour-Minute-Second */
|
||||
s->regs[REG_HHMMSS] = val;
|
||||
s->regs[REG_LOSC] |= REG_LOSC_HMS;
|
||||
break;
|
||||
case REG_GP0: /* General Purpose Register 0 */
|
||||
case REG_GP1: /* General Purpose Register 1 */
|
||||
case REG_GP2: /* General Purpose Register 2 */
|
||||
case REG_GP3: /* General Purpose Register 3 */
|
||||
s->regs[c->regmap[offset]] = val;
|
||||
break;
|
||||
default:
|
||||
if (!c->write(s, offset, val)) {
|
||||
qemu_log_mask(LOG_UNIMP, "%s: unimplemented register 0x%04x\n",
|
||||
__func__, (uint32_t)offset);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps allwinner_rtc_ops = {
|
||||
.read = allwinner_rtc_read,
|
||||
.write = allwinner_rtc_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
.impl.min_access_size = 4,
|
||||
};
|
||||
|
||||
static void allwinner_rtc_reset(DeviceState *dev)
|
||||
{
|
||||
AwRtcState *s = AW_RTC(dev);
|
||||
struct tm now;
|
||||
|
||||
/* Clear registers */
|
||||
memset(s->regs, 0, sizeof(s->regs));
|
||||
|
||||
/* Get current datetime */
|
||||
qemu_get_timedate(&now, 0);
|
||||
|
||||
/* Set RTC with current datetime */
|
||||
if (s->base_year > 1900) {
|
||||
s->regs[REG_YYMMDD] = ((now.tm_year + 1900 - s->base_year) << 16) |
|
||||
((now.tm_mon + 1) << 8) |
|
||||
now.tm_mday;
|
||||
s->regs[REG_HHMMSS] = (((now.tm_wday + 6) % 7) << 29) |
|
||||
(now.tm_hour << 16) |
|
||||
(now.tm_min << 8) |
|
||||
now.tm_sec;
|
||||
}
|
||||
}
|
||||
|
||||
static void allwinner_rtc_init(Object *obj)
|
||||
{
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
AwRtcState *s = AW_RTC(obj);
|
||||
|
||||
/* Memory mapping */
|
||||
memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_rtc_ops, s,
|
||||
TYPE_AW_RTC, 1 * KiB);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
}
|
||||
|
||||
static const VMStateDescription allwinner_rtc_vmstate = {
|
||||
.name = "allwinner-rtc",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32_ARRAY(regs, AwRtcState, AW_RTC_REGS_NUM),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static Property allwinner_rtc_properties[] = {
|
||||
DEFINE_PROP_INT32("base-year", AwRtcState, base_year, 0),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void allwinner_rtc_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->reset = allwinner_rtc_reset;
|
||||
dc->vmsd = &allwinner_rtc_vmstate;
|
||||
device_class_set_props(dc, allwinner_rtc_properties);
|
||||
}
|
||||
|
||||
static void allwinner_rtc_sun4i_init(Object *obj)
|
||||
{
|
||||
AwRtcState *s = AW_RTC(obj);
|
||||
s->base_year = 2010;
|
||||
}
|
||||
|
||||
static void allwinner_rtc_sun4i_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
AwRtcClass *arc = AW_RTC_CLASS(klass);
|
||||
|
||||
arc->regmap = allwinner_rtc_sun4i_regmap;
|
||||
arc->regmap_size = sizeof(allwinner_rtc_sun4i_regmap);
|
||||
arc->read = allwinner_rtc_sun4i_read;
|
||||
arc->write = allwinner_rtc_sun4i_write;
|
||||
}
|
||||
|
||||
static void allwinner_rtc_sun6i_init(Object *obj)
|
||||
{
|
||||
AwRtcState *s = AW_RTC(obj);
|
||||
s->base_year = 1970;
|
||||
}
|
||||
|
||||
static void allwinner_rtc_sun6i_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
AwRtcClass *arc = AW_RTC_CLASS(klass);
|
||||
|
||||
arc->regmap = allwinner_rtc_sun6i_regmap;
|
||||
arc->regmap_size = sizeof(allwinner_rtc_sun6i_regmap);
|
||||
arc->read = allwinner_rtc_sun6i_read;
|
||||
arc->write = allwinner_rtc_sun6i_write;
|
||||
}
|
||||
|
||||
static void allwinner_rtc_sun7i_init(Object *obj)
|
||||
{
|
||||
AwRtcState *s = AW_RTC(obj);
|
||||
s->base_year = 1970;
|
||||
}
|
||||
|
||||
static void allwinner_rtc_sun7i_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
AwRtcClass *arc = AW_RTC_CLASS(klass);
|
||||
allwinner_rtc_sun4i_class_init(klass, arc);
|
||||
}
|
||||
|
||||
static const TypeInfo allwinner_rtc_info = {
|
||||
.name = TYPE_AW_RTC,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_init = allwinner_rtc_init,
|
||||
.instance_size = sizeof(AwRtcState),
|
||||
.class_init = allwinner_rtc_class_init,
|
||||
.class_size = sizeof(AwRtcClass),
|
||||
.abstract = true,
|
||||
};
|
||||
|
||||
static const TypeInfo allwinner_rtc_sun4i_info = {
|
||||
.name = TYPE_AW_RTC_SUN4I,
|
||||
.parent = TYPE_AW_RTC,
|
||||
.class_init = allwinner_rtc_sun4i_class_init,
|
||||
.instance_init = allwinner_rtc_sun4i_init,
|
||||
};
|
||||
|
||||
static const TypeInfo allwinner_rtc_sun6i_info = {
|
||||
.name = TYPE_AW_RTC_SUN6I,
|
||||
.parent = TYPE_AW_RTC,
|
||||
.class_init = allwinner_rtc_sun6i_class_init,
|
||||
.instance_init = allwinner_rtc_sun6i_init,
|
||||
};
|
||||
|
||||
static const TypeInfo allwinner_rtc_sun7i_info = {
|
||||
.name = TYPE_AW_RTC_SUN7I,
|
||||
.parent = TYPE_AW_RTC,
|
||||
.class_init = allwinner_rtc_sun7i_class_init,
|
||||
.instance_init = allwinner_rtc_sun7i_init,
|
||||
};
|
||||
|
||||
static void allwinner_rtc_register(void)
|
||||
{
|
||||
type_register_static(&allwinner_rtc_info);
|
||||
type_register_static(&allwinner_rtc_sun4i_info);
|
||||
type_register_static(&allwinner_rtc_sun6i_info);
|
||||
type_register_static(&allwinner_rtc_sun7i_info);
|
||||
}
|
||||
|
||||
type_init(allwinner_rtc_register)
|
@ -1,5 +1,9 @@
|
||||
# See docs/devel/tracing.txt for syntax documentation.
|
||||
|
||||
# allwinner-rtc.c
|
||||
allwinner_rtc_read(uint64_t addr, uint64_t value) "addr 0x%" PRIx64 " value 0x%" PRIx64
|
||||
allwinner_rtc_write(uint64_t addr, uint64_t value) "addr 0x%" PRIx64 " value 0x%" PRIx64
|
||||
|
||||
# sun4v-rtc.c
|
||||
sun4v_rtc_read(uint64_t addr, uint64_t value) "read: addr 0x%" PRIx64 " value 0x%" PRIx64
|
||||
sun4v_rtc_write(uint64_t addr, uint64_t value) "write: addr 0x%" PRIx64 " value 0x%" PRIx64
|
||||
|
@ -4,6 +4,7 @@ common-obj-$(CONFIG_SD) += sd.o core.o sdmmc-internal.o
|
||||
common-obj-$(CONFIG_SDHCI) += sdhci.o
|
||||
common-obj-$(CONFIG_SDHCI_PCI) += sdhci-pci.o
|
||||
|
||||
common-obj-$(CONFIG_ALLWINNER_H3) += allwinner-sdhost.o
|
||||
common-obj-$(CONFIG_MILKYMIST) += milkymist-memcard.o
|
||||
common-obj-$(CONFIG_OMAP) += omap_mmc.o
|
||||
common-obj-$(CONFIG_PXA2XX) += pxa2xx_mmci.o
|
||||
|
854
hw/sd/allwinner-sdhost.c
Normal file
854
hw/sd/allwinner-sdhost.c
Normal file
@ -0,0 +1,854 @@
|
||||
/*
|
||||
* Allwinner (sun4i and above) SD Host Controller emulation
|
||||
*
|
||||
* Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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/log.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qemu/units.h"
|
||||
#include "sysemu/blockdev.h"
|
||||
#include "hw/irq.h"
|
||||
#include "hw/sd/allwinner-sdhost.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "trace.h"
|
||||
|
||||
#define TYPE_AW_SDHOST_BUS "allwinner-sdhost-bus"
|
||||
#define AW_SDHOST_BUS(obj) \
|
||||
OBJECT_CHECK(SDBus, (obj), TYPE_AW_SDHOST_BUS)
|
||||
|
||||
/* SD Host register offsets */
|
||||
enum {
|
||||
REG_SD_GCTL = 0x00, /* Global Control */
|
||||
REG_SD_CKCR = 0x04, /* Clock Control */
|
||||
REG_SD_TMOR = 0x08, /* Timeout */
|
||||
REG_SD_BWDR = 0x0C, /* Bus Width */
|
||||
REG_SD_BKSR = 0x10, /* Block Size */
|
||||
REG_SD_BYCR = 0x14, /* Byte Count */
|
||||
REG_SD_CMDR = 0x18, /* Command */
|
||||
REG_SD_CAGR = 0x1C, /* Command Argument */
|
||||
REG_SD_RESP0 = 0x20, /* Response Zero */
|
||||
REG_SD_RESP1 = 0x24, /* Response One */
|
||||
REG_SD_RESP2 = 0x28, /* Response Two */
|
||||
REG_SD_RESP3 = 0x2C, /* Response Three */
|
||||
REG_SD_IMKR = 0x30, /* Interrupt Mask */
|
||||
REG_SD_MISR = 0x34, /* Masked Interrupt Status */
|
||||
REG_SD_RISR = 0x38, /* Raw Interrupt Status */
|
||||
REG_SD_STAR = 0x3C, /* Status */
|
||||
REG_SD_FWLR = 0x40, /* FIFO Water Level */
|
||||
REG_SD_FUNS = 0x44, /* FIFO Function Select */
|
||||
REG_SD_DBGC = 0x50, /* Debug Enable */
|
||||
REG_SD_A12A = 0x58, /* Auto command 12 argument */
|
||||
REG_SD_NTSR = 0x5C, /* SD NewTiming Set */
|
||||
REG_SD_SDBG = 0x60, /* SD newTiming Set Debug */
|
||||
REG_SD_HWRST = 0x78, /* Hardware Reset Register */
|
||||
REG_SD_DMAC = 0x80, /* Internal DMA Controller Control */
|
||||
REG_SD_DLBA = 0x84, /* Descriptor List Base Address */
|
||||
REG_SD_IDST = 0x88, /* Internal DMA Controller Status */
|
||||
REG_SD_IDIE = 0x8C, /* Internal DMA Controller IRQ Enable */
|
||||
REG_SD_THLDC = 0x100, /* Card Threshold Control */
|
||||
REG_SD_DSBD = 0x10C, /* eMMC DDR Start Bit Detection Control */
|
||||
REG_SD_RES_CRC = 0x110, /* Response CRC from card/eMMC */
|
||||
REG_SD_DATA7_CRC = 0x114, /* CRC Data 7 from card/eMMC */
|
||||
REG_SD_DATA6_CRC = 0x118, /* CRC Data 6 from card/eMMC */
|
||||
REG_SD_DATA5_CRC = 0x11C, /* CRC Data 5 from card/eMMC */
|
||||
REG_SD_DATA4_CRC = 0x120, /* CRC Data 4 from card/eMMC */
|
||||
REG_SD_DATA3_CRC = 0x124, /* CRC Data 3 from card/eMMC */
|
||||
REG_SD_DATA2_CRC = 0x128, /* CRC Data 2 from card/eMMC */
|
||||
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_FIFO = 0x200, /* Read/Write FIFO */
|
||||
};
|
||||
|
||||
/* SD Host register flags */
|
||||
enum {
|
||||
SD_GCTL_FIFO_AC_MOD = (1 << 31),
|
||||
SD_GCTL_DDR_MOD_SEL = (1 << 10),
|
||||
SD_GCTL_CD_DBC_ENB = (1 << 8),
|
||||
SD_GCTL_DMA_ENB = (1 << 5),
|
||||
SD_GCTL_INT_ENB = (1 << 4),
|
||||
SD_GCTL_DMA_RST = (1 << 2),
|
||||
SD_GCTL_FIFO_RST = (1 << 1),
|
||||
SD_GCTL_SOFT_RST = (1 << 0),
|
||||
};
|
||||
|
||||
enum {
|
||||
SD_CMDR_LOAD = (1 << 31),
|
||||
SD_CMDR_CLKCHANGE = (1 << 21),
|
||||
SD_CMDR_WRITE = (1 << 10),
|
||||
SD_CMDR_AUTOSTOP = (1 << 12),
|
||||
SD_CMDR_DATA = (1 << 9),
|
||||
SD_CMDR_RESPONSE_LONG = (1 << 7),
|
||||
SD_CMDR_RESPONSE = (1 << 6),
|
||||
SD_CMDR_CMDID_MASK = (0x3f),
|
||||
};
|
||||
|
||||
enum {
|
||||
SD_RISR_CARD_REMOVE = (1 << 31),
|
||||
SD_RISR_CARD_INSERT = (1 << 30),
|
||||
SD_RISR_SDIO_INTR = (1 << 16),
|
||||
SD_RISR_AUTOCMD_DONE = (1 << 14),
|
||||
SD_RISR_DATA_COMPLETE = (1 << 3),
|
||||
SD_RISR_CMD_COMPLETE = (1 << 2),
|
||||
SD_RISR_NO_RESPONSE = (1 << 1),
|
||||
};
|
||||
|
||||
enum {
|
||||
SD_STAR_CARD_PRESENT = (1 << 8),
|
||||
};
|
||||
|
||||
enum {
|
||||
SD_IDST_INT_SUMMARY = (1 << 8),
|
||||
SD_IDST_RECEIVE_IRQ = (1 << 1),
|
||||
SD_IDST_TRANSMIT_IRQ = (1 << 0),
|
||||
SD_IDST_IRQ_MASK = (1 << 1) | (1 << 0) | (1 << 8),
|
||||
SD_IDST_WR_MASK = (0x3ff),
|
||||
};
|
||||
|
||||
/* SD Host register reset values */
|
||||
enum {
|
||||
REG_SD_GCTL_RST = 0x00000300,
|
||||
REG_SD_CKCR_RST = 0x0,
|
||||
REG_SD_TMOR_RST = 0xFFFFFF40,
|
||||
REG_SD_BWDR_RST = 0x0,
|
||||
REG_SD_BKSR_RST = 0x00000200,
|
||||
REG_SD_BYCR_RST = 0x00000200,
|
||||
REG_SD_CMDR_RST = 0x0,
|
||||
REG_SD_CAGR_RST = 0x0,
|
||||
REG_SD_RESP_RST = 0x0,
|
||||
REG_SD_IMKR_RST = 0x0,
|
||||
REG_SD_MISR_RST = 0x0,
|
||||
REG_SD_RISR_RST = 0x0,
|
||||
REG_SD_STAR_RST = 0x00000100,
|
||||
REG_SD_FWLR_RST = 0x000F0000,
|
||||
REG_SD_FUNS_RST = 0x0,
|
||||
REG_SD_DBGC_RST = 0x0,
|
||||
REG_SD_A12A_RST = 0x0000FFFF,
|
||||
REG_SD_NTSR_RST = 0x00000001,
|
||||
REG_SD_SDBG_RST = 0x0,
|
||||
REG_SD_HWRST_RST = 0x00000001,
|
||||
REG_SD_DMAC_RST = 0x0,
|
||||
REG_SD_DLBA_RST = 0x0,
|
||||
REG_SD_IDST_RST = 0x0,
|
||||
REG_SD_IDIE_RST = 0x0,
|
||||
REG_SD_THLDC_RST = 0x0,
|
||||
REG_SD_DSBD_RST = 0x0,
|
||||
REG_SD_RES_CRC_RST = 0x0,
|
||||
REG_SD_DATA_CRC_RST = 0x0,
|
||||
REG_SD_CRC_STA_RST = 0x0,
|
||||
REG_SD_FIFO_RST = 0x0,
|
||||
};
|
||||
|
||||
/* Data transfer descriptor for DMA */
|
||||
typedef struct TransferDescriptor {
|
||||
uint32_t status; /* Status flags */
|
||||
uint32_t size; /* Data buffer size */
|
||||
uint32_t addr; /* Data buffer address */
|
||||
uint32_t next; /* Physical address of next descriptor */
|
||||
} TransferDescriptor;
|
||||
|
||||
/* Data transfer descriptor flags */
|
||||
enum {
|
||||
DESC_STATUS_HOLD = (1 << 31), /* Set when descriptor is in use by DMA */
|
||||
DESC_STATUS_ERROR = (1 << 30), /* Set when DMA transfer error occurred */
|
||||
DESC_STATUS_CHAIN = (1 << 4), /* Indicates chained descriptor. */
|
||||
DESC_STATUS_FIRST = (1 << 3), /* Set on the first descriptor */
|
||||
DESC_STATUS_LAST = (1 << 2), /* Set on the last descriptor */
|
||||
DESC_STATUS_NOIRQ = (1 << 1), /* Skip raising interrupt after transfer */
|
||||
DESC_SIZE_MASK = (0xfffffffc)
|
||||
};
|
||||
|
||||
static void allwinner_sdhost_update_irq(AwSdHostState *s)
|
||||
{
|
||||
uint32_t irq;
|
||||
|
||||
if (s->global_ctl & SD_GCTL_INT_ENB) {
|
||||
irq = s->irq_status & s->irq_mask;
|
||||
} else {
|
||||
irq = 0;
|
||||
}
|
||||
|
||||
trace_allwinner_sdhost_update_irq(irq);
|
||||
qemu_set_irq(s->irq, irq);
|
||||
}
|
||||
|
||||
static void allwinner_sdhost_update_transfer_cnt(AwSdHostState *s,
|
||||
uint32_t bytes)
|
||||
{
|
||||
if (s->transfer_cnt > bytes) {
|
||||
s->transfer_cnt -= bytes;
|
||||
} else {
|
||||
s->transfer_cnt = 0;
|
||||
}
|
||||
|
||||
if (!s->transfer_cnt) {
|
||||
s->irq_status |= SD_RISR_DATA_COMPLETE;
|
||||
}
|
||||
}
|
||||
|
||||
static void allwinner_sdhost_set_inserted(DeviceState *dev, bool inserted)
|
||||
{
|
||||
AwSdHostState *s = AW_SDHOST(dev);
|
||||
|
||||
trace_allwinner_sdhost_set_inserted(inserted);
|
||||
|
||||
if (inserted) {
|
||||
s->irq_status |= SD_RISR_CARD_INSERT;
|
||||
s->irq_status &= ~SD_RISR_CARD_REMOVE;
|
||||
s->status |= SD_STAR_CARD_PRESENT;
|
||||
} else {
|
||||
s->irq_status &= ~SD_RISR_CARD_INSERT;
|
||||
s->irq_status |= SD_RISR_CARD_REMOVE;
|
||||
s->status &= ~SD_STAR_CARD_PRESENT;
|
||||
}
|
||||
|
||||
allwinner_sdhost_update_irq(s);
|
||||
}
|
||||
|
||||
static void allwinner_sdhost_send_command(AwSdHostState *s)
|
||||
{
|
||||
SDRequest request;
|
||||
uint8_t resp[16];
|
||||
int rlen;
|
||||
|
||||
/* Auto clear load flag */
|
||||
s->command &= ~SD_CMDR_LOAD;
|
||||
|
||||
/* Clock change does not actually interact with the SD bus */
|
||||
if (!(s->command & SD_CMDR_CLKCHANGE)) {
|
||||
|
||||
/* Prepare request */
|
||||
request.cmd = s->command & SD_CMDR_CMDID_MASK;
|
||||
request.arg = s->command_arg;
|
||||
|
||||
/* Send request to SD bus */
|
||||
rlen = sdbus_do_command(&s->sdbus, &request, resp);
|
||||
if (rlen < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* If the command has a response, store it in the response registers */
|
||||
if ((s->command & SD_CMDR_RESPONSE)) {
|
||||
if (rlen == 4 && !(s->command & SD_CMDR_RESPONSE_LONG)) {
|
||||
s->response[0] = ldl_be_p(&resp[0]);
|
||||
s->response[1] = s->response[2] = s->response[3] = 0;
|
||||
|
||||
} else if (rlen == 16 && (s->command & SD_CMDR_RESPONSE_LONG)) {
|
||||
s->response[0] = ldl_be_p(&resp[12]);
|
||||
s->response[1] = ldl_be_p(&resp[8]);
|
||||
s->response[2] = ldl_be_p(&resp[4]);
|
||||
s->response[3] = ldl_be_p(&resp[0]);
|
||||
} else {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Set interrupt status bits */
|
||||
s->irq_status |= SD_RISR_CMD_COMPLETE;
|
||||
return;
|
||||
|
||||
error:
|
||||
s->irq_status |= SD_RISR_NO_RESPONSE;
|
||||
}
|
||||
|
||||
static void allwinner_sdhost_auto_stop(AwSdHostState *s)
|
||||
{
|
||||
/*
|
||||
* The stop command (CMD12) ensures the SD bus
|
||||
* returns to the transfer state.
|
||||
*/
|
||||
if ((s->command & SD_CMDR_AUTOSTOP) && (s->transfer_cnt == 0)) {
|
||||
/* First save current command registers */
|
||||
uint32_t saved_cmd = s->command;
|
||||
uint32_t saved_arg = s->command_arg;
|
||||
|
||||
/* Prepare stop command (CMD12) */
|
||||
s->command &= ~SD_CMDR_CMDID_MASK;
|
||||
s->command |= 12; /* CMD12 */
|
||||
s->command_arg = 0;
|
||||
|
||||
/* Put the command on SD bus */
|
||||
allwinner_sdhost_send_command(s);
|
||||
|
||||
/* Restore command values */
|
||||
s->command = saved_cmd;
|
||||
s->command_arg = saved_arg;
|
||||
|
||||
/* Set IRQ status bit for automatic stop done */
|
||||
s->irq_status |= SD_RISR_AUTOCMD_DONE;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t allwinner_sdhost_process_desc(AwSdHostState *s,
|
||||
hwaddr desc_addr,
|
||||
TransferDescriptor *desc,
|
||||
bool is_write, uint32_t max_bytes)
|
||||
{
|
||||
AwSdHostClass *klass = AW_SDHOST_GET_CLASS(s);
|
||||
uint32_t num_done = 0;
|
||||
uint32_t num_bytes = max_bytes;
|
||||
uint8_t buf[1024];
|
||||
|
||||
/* Read descriptor */
|
||||
cpu_physical_memory_read(desc_addr, desc, sizeof(*desc));
|
||||
if (desc->size == 0) {
|
||||
desc->size = klass->max_desc_size;
|
||||
} else if (desc->size > klass->max_desc_size) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: DMA descriptor buffer size "
|
||||
" is out-of-bounds: %" PRIu32 " > %zu",
|
||||
__func__, desc->size, klass->max_desc_size);
|
||||
desc->size = klass->max_desc_size;
|
||||
}
|
||||
if (desc->size < num_bytes) {
|
||||
num_bytes = desc->size;
|
||||
}
|
||||
|
||||
trace_allwinner_sdhost_process_desc(desc_addr, desc->size,
|
||||
is_write, max_bytes);
|
||||
|
||||
while (num_done < num_bytes) {
|
||||
/* Try to completely fill the local buffer */
|
||||
uint32_t buf_bytes = num_bytes - num_done;
|
||||
if (buf_bytes > sizeof(buf)) {
|
||||
buf_bytes = sizeof(buf);
|
||||
}
|
||||
|
||||
/* Write to SD bus */
|
||||
if (is_write) {
|
||||
cpu_physical_memory_read((desc->addr & DESC_SIZE_MASK) + num_done,
|
||||
buf, buf_bytes);
|
||||
|
||||
for (uint32_t i = 0; i < buf_bytes; i++) {
|
||||
sdbus_write_data(&s->sdbus, buf[i]);
|
||||
}
|
||||
|
||||
/* Read from SD bus */
|
||||
} else {
|
||||
for (uint32_t i = 0; i < buf_bytes; i++) {
|
||||
buf[i] = sdbus_read_data(&s->sdbus);
|
||||
}
|
||||
cpu_physical_memory_write((desc->addr & DESC_SIZE_MASK) + num_done,
|
||||
buf, buf_bytes);
|
||||
}
|
||||
num_done += buf_bytes;
|
||||
}
|
||||
|
||||
/* Clear hold flag and flush descriptor */
|
||||
desc->status &= ~DESC_STATUS_HOLD;
|
||||
cpu_physical_memory_write(desc_addr, desc, sizeof(*desc));
|
||||
|
||||
return num_done;
|
||||
}
|
||||
|
||||
static void allwinner_sdhost_dma(AwSdHostState *s)
|
||||
{
|
||||
TransferDescriptor desc;
|
||||
hwaddr desc_addr = s->desc_base;
|
||||
bool is_write = (s->command & SD_CMDR_WRITE);
|
||||
uint32_t bytes_done = 0;
|
||||
|
||||
/* Check if DMA can be performed */
|
||||
if (s->byte_count == 0 || s->block_size == 0 ||
|
||||
!(s->global_ctl & SD_GCTL_DMA_ENB)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* For read operations, data must be available on the SD bus
|
||||
* If not, it is an error and we should not act at all
|
||||
*/
|
||||
if (!is_write && !sdbus_data_ready(&s->sdbus)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Process the DMA descriptors until all data is copied */
|
||||
while (s->byte_count > 0) {
|
||||
bytes_done = allwinner_sdhost_process_desc(s, desc_addr, &desc,
|
||||
is_write, s->byte_count);
|
||||
allwinner_sdhost_update_transfer_cnt(s, bytes_done);
|
||||
|
||||
if (bytes_done <= s->byte_count) {
|
||||
s->byte_count -= bytes_done;
|
||||
} else {
|
||||
s->byte_count = 0;
|
||||
}
|
||||
|
||||
if (desc.status & DESC_STATUS_LAST) {
|
||||
break;
|
||||
} else {
|
||||
desc_addr = desc.next;
|
||||
}
|
||||
}
|
||||
|
||||
/* Raise IRQ to signal DMA is completed */
|
||||
s->irq_status |= SD_RISR_DATA_COMPLETE | SD_RISR_SDIO_INTR;
|
||||
|
||||
/* Update DMAC bits */
|
||||
s->dmac_status |= SD_IDST_INT_SUMMARY;
|
||||
|
||||
if (is_write) {
|
||||
s->dmac_status |= SD_IDST_TRANSMIT_IRQ;
|
||||
} else {
|
||||
s->dmac_status |= SD_IDST_RECEIVE_IRQ;
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t allwinner_sdhost_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
AwSdHostState *s = AW_SDHOST(opaque);
|
||||
uint32_t res = 0;
|
||||
|
||||
switch (offset) {
|
||||
case REG_SD_GCTL: /* Global Control */
|
||||
res = s->global_ctl;
|
||||
break;
|
||||
case REG_SD_CKCR: /* Clock Control */
|
||||
res = s->clock_ctl;
|
||||
break;
|
||||
case REG_SD_TMOR: /* Timeout */
|
||||
res = s->timeout;
|
||||
break;
|
||||
case REG_SD_BWDR: /* Bus Width */
|
||||
res = s->bus_width;
|
||||
break;
|
||||
case REG_SD_BKSR: /* Block Size */
|
||||
res = s->block_size;
|
||||
break;
|
||||
case REG_SD_BYCR: /* Byte Count */
|
||||
res = s->byte_count;
|
||||
break;
|
||||
case REG_SD_CMDR: /* Command */
|
||||
res = s->command;
|
||||
break;
|
||||
case REG_SD_CAGR: /* Command Argument */
|
||||
res = s->command_arg;
|
||||
break;
|
||||
case REG_SD_RESP0: /* Response Zero */
|
||||
res = s->response[0];
|
||||
break;
|
||||
case REG_SD_RESP1: /* Response One */
|
||||
res = s->response[1];
|
||||
break;
|
||||
case REG_SD_RESP2: /* Response Two */
|
||||
res = s->response[2];
|
||||
break;
|
||||
case REG_SD_RESP3: /* Response Three */
|
||||
res = s->response[3];
|
||||
break;
|
||||
case REG_SD_IMKR: /* Interrupt Mask */
|
||||
res = s->irq_mask;
|
||||
break;
|
||||
case REG_SD_MISR: /* Masked Interrupt Status */
|
||||
res = s->irq_status & s->irq_mask;
|
||||
break;
|
||||
case REG_SD_RISR: /* Raw Interrupt Status */
|
||||
res = s->irq_status;
|
||||
break;
|
||||
case REG_SD_STAR: /* Status */
|
||||
res = s->status;
|
||||
break;
|
||||
case REG_SD_FWLR: /* FIFO Water Level */
|
||||
res = s->fifo_wlevel;
|
||||
break;
|
||||
case REG_SD_FUNS: /* FIFO Function Select */
|
||||
res = s->fifo_func_sel;
|
||||
break;
|
||||
case REG_SD_DBGC: /* Debug Enable */
|
||||
res = s->debug_enable;
|
||||
break;
|
||||
case REG_SD_A12A: /* Auto command 12 argument */
|
||||
res = s->auto12_arg;
|
||||
break;
|
||||
case REG_SD_NTSR: /* SD NewTiming Set */
|
||||
res = s->newtiming_set;
|
||||
break;
|
||||
case REG_SD_SDBG: /* SD newTiming Set Debug */
|
||||
res = s->newtiming_debug;
|
||||
break;
|
||||
case REG_SD_HWRST: /* Hardware Reset Register */
|
||||
res = s->hardware_rst;
|
||||
break;
|
||||
case REG_SD_DMAC: /* Internal DMA Controller Control */
|
||||
res = s->dmac;
|
||||
break;
|
||||
case REG_SD_DLBA: /* Descriptor List Base Address */
|
||||
res = s->desc_base;
|
||||
break;
|
||||
case REG_SD_IDST: /* Internal DMA Controller Status */
|
||||
res = s->dmac_status;
|
||||
break;
|
||||
case REG_SD_IDIE: /* Internal DMA Controller Interrupt Enable */
|
||||
res = s->dmac_irq;
|
||||
break;
|
||||
case REG_SD_THLDC: /* Card Threshold Control */
|
||||
res = s->card_threshold;
|
||||
break;
|
||||
case REG_SD_DSBD: /* eMMC DDR Start Bit Detection Control */
|
||||
res = s->startbit_detect;
|
||||
break;
|
||||
case REG_SD_RES_CRC: /* Response CRC from card/eMMC */
|
||||
res = s->response_crc;
|
||||
break;
|
||||
case REG_SD_DATA7_CRC: /* CRC Data 7 from card/eMMC */
|
||||
case REG_SD_DATA6_CRC: /* CRC Data 6 from card/eMMC */
|
||||
case REG_SD_DATA5_CRC: /* CRC Data 5 from card/eMMC */
|
||||
case REG_SD_DATA4_CRC: /* CRC Data 4 from card/eMMC */
|
||||
case REG_SD_DATA3_CRC: /* CRC Data 3 from card/eMMC */
|
||||
case REG_SD_DATA2_CRC: /* CRC Data 2 from card/eMMC */
|
||||
case REG_SD_DATA1_CRC: /* CRC Data 1 from card/eMMC */
|
||||
case REG_SD_DATA0_CRC: /* CRC Data 0 from card/eMMC */
|
||||
res = s->data_crc[((offset - REG_SD_DATA7_CRC) / sizeof(uint32_t))];
|
||||
break;
|
||||
case REG_SD_CRC_STA: /* CRC status from card/eMMC in write operation */
|
||||
res = s->status_crc;
|
||||
break;
|
||||
case REG_SD_FIFO: /* Read/Write FIFO */
|
||||
if (sdbus_data_ready(&s->sdbus)) {
|
||||
res = sdbus_read_data(&s->sdbus);
|
||||
res |= sdbus_read_data(&s->sdbus) << 8;
|
||||
res |= sdbus_read_data(&s->sdbus) << 16;
|
||||
res |= sdbus_read_data(&s->sdbus) << 24;
|
||||
allwinner_sdhost_update_transfer_cnt(s, sizeof(uint32_t));
|
||||
allwinner_sdhost_auto_stop(s);
|
||||
allwinner_sdhost_update_irq(s);
|
||||
} else {
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: no data ready on SD bus\n",
|
||||
__func__);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset %"
|
||||
HWADDR_PRIx"\n", __func__, offset);
|
||||
res = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
trace_allwinner_sdhost_read(offset, res, size);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void allwinner_sdhost_write(void *opaque, hwaddr offset,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
AwSdHostState *s = AW_SDHOST(opaque);
|
||||
|
||||
trace_allwinner_sdhost_write(offset, value, size);
|
||||
|
||||
switch (offset) {
|
||||
case REG_SD_GCTL: /* Global Control */
|
||||
s->global_ctl = value;
|
||||
s->global_ctl &= ~(SD_GCTL_DMA_RST | SD_GCTL_FIFO_RST |
|
||||
SD_GCTL_SOFT_RST);
|
||||
allwinner_sdhost_update_irq(s);
|
||||
break;
|
||||
case REG_SD_CKCR: /* Clock Control */
|
||||
s->clock_ctl = value;
|
||||
break;
|
||||
case REG_SD_TMOR: /* Timeout */
|
||||
s->timeout = value;
|
||||
break;
|
||||
case REG_SD_BWDR: /* Bus Width */
|
||||
s->bus_width = value;
|
||||
break;
|
||||
case REG_SD_BKSR: /* Block Size */
|
||||
s->block_size = value;
|
||||
break;
|
||||
case REG_SD_BYCR: /* Byte Count */
|
||||
s->byte_count = value;
|
||||
s->transfer_cnt = value;
|
||||
break;
|
||||
case REG_SD_CMDR: /* Command */
|
||||
s->command = value;
|
||||
if (value & SD_CMDR_LOAD) {
|
||||
allwinner_sdhost_send_command(s);
|
||||
allwinner_sdhost_dma(s);
|
||||
allwinner_sdhost_auto_stop(s);
|
||||
}
|
||||
allwinner_sdhost_update_irq(s);
|
||||
break;
|
||||
case REG_SD_CAGR: /* Command Argument */
|
||||
s->command_arg = value;
|
||||
break;
|
||||
case REG_SD_RESP0: /* Response Zero */
|
||||
s->response[0] = value;
|
||||
break;
|
||||
case REG_SD_RESP1: /* Response One */
|
||||
s->response[1] = value;
|
||||
break;
|
||||
case REG_SD_RESP2: /* Response Two */
|
||||
s->response[2] = value;
|
||||
break;
|
||||
case REG_SD_RESP3: /* Response Three */
|
||||
s->response[3] = value;
|
||||
break;
|
||||
case REG_SD_IMKR: /* Interrupt Mask */
|
||||
s->irq_mask = value;
|
||||
allwinner_sdhost_update_irq(s);
|
||||
break;
|
||||
case REG_SD_MISR: /* Masked Interrupt Status */
|
||||
case REG_SD_RISR: /* Raw Interrupt Status */
|
||||
s->irq_status &= ~value;
|
||||
allwinner_sdhost_update_irq(s);
|
||||
break;
|
||||
case REG_SD_STAR: /* Status */
|
||||
s->status &= ~value;
|
||||
allwinner_sdhost_update_irq(s);
|
||||
break;
|
||||
case REG_SD_FWLR: /* FIFO Water Level */
|
||||
s->fifo_wlevel = value;
|
||||
break;
|
||||
case REG_SD_FUNS: /* FIFO Function Select */
|
||||
s->fifo_func_sel = value;
|
||||
break;
|
||||
case REG_SD_DBGC: /* Debug Enable */
|
||||
s->debug_enable = value;
|
||||
break;
|
||||
case REG_SD_A12A: /* Auto command 12 argument */
|
||||
s->auto12_arg = value;
|
||||
break;
|
||||
case REG_SD_NTSR: /* SD NewTiming Set */
|
||||
s->newtiming_set = value;
|
||||
break;
|
||||
case REG_SD_SDBG: /* SD newTiming Set Debug */
|
||||
s->newtiming_debug = value;
|
||||
break;
|
||||
case REG_SD_HWRST: /* Hardware Reset Register */
|
||||
s->hardware_rst = value;
|
||||
break;
|
||||
case REG_SD_DMAC: /* Internal DMA Controller Control */
|
||||
s->dmac = value;
|
||||
allwinner_sdhost_update_irq(s);
|
||||
break;
|
||||
case REG_SD_DLBA: /* Descriptor List Base Address */
|
||||
s->desc_base = value;
|
||||
break;
|
||||
case REG_SD_IDST: /* Internal DMA Controller Status */
|
||||
s->dmac_status &= (~SD_IDST_WR_MASK) | (~value & SD_IDST_WR_MASK);
|
||||
allwinner_sdhost_update_irq(s);
|
||||
break;
|
||||
case REG_SD_IDIE: /* Internal DMA Controller Interrupt Enable */
|
||||
s->dmac_irq = value;
|
||||
allwinner_sdhost_update_irq(s);
|
||||
break;
|
||||
case REG_SD_THLDC: /* Card Threshold Control */
|
||||
s->card_threshold = value;
|
||||
break;
|
||||
case REG_SD_DSBD: /* eMMC DDR Start Bit Detection Control */
|
||||
s->startbit_detect = value;
|
||||
break;
|
||||
case REG_SD_FIFO: /* Read/Write FIFO */
|
||||
sdbus_write_data(&s->sdbus, value & 0xff);
|
||||
sdbus_write_data(&s->sdbus, (value >> 8) & 0xff);
|
||||
sdbus_write_data(&s->sdbus, (value >> 16) & 0xff);
|
||||
sdbus_write_data(&s->sdbus, (value >> 24) & 0xff);
|
||||
allwinner_sdhost_update_transfer_cnt(s, sizeof(uint32_t));
|
||||
allwinner_sdhost_auto_stop(s);
|
||||
allwinner_sdhost_update_irq(s);
|
||||
break;
|
||||
case REG_SD_RES_CRC: /* Response CRC from card/eMMC */
|
||||
case REG_SD_DATA7_CRC: /* CRC Data 7 from card/eMMC */
|
||||
case REG_SD_DATA6_CRC: /* CRC Data 6 from card/eMMC */
|
||||
case REG_SD_DATA5_CRC: /* CRC Data 5 from card/eMMC */
|
||||
case REG_SD_DATA4_CRC: /* CRC Data 4 from card/eMMC */
|
||||
case REG_SD_DATA3_CRC: /* CRC Data 3 from card/eMMC */
|
||||
case REG_SD_DATA2_CRC: /* CRC Data 2 from card/eMMC */
|
||||
case REG_SD_DATA1_CRC: /* CRC Data 1 from card/eMMC */
|
||||
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;
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset %"
|
||||
HWADDR_PRIx"\n", __func__, offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps allwinner_sdhost_ops = {
|
||||
.read = allwinner_sdhost_read,
|
||||
.write = allwinner_sdhost_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
.impl.min_access_size = 4,
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_allwinner_sdhost = {
|
||||
.name = "allwinner-sdhost",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32(global_ctl, AwSdHostState),
|
||||
VMSTATE_UINT32(clock_ctl, AwSdHostState),
|
||||
VMSTATE_UINT32(timeout, AwSdHostState),
|
||||
VMSTATE_UINT32(bus_width, AwSdHostState),
|
||||
VMSTATE_UINT32(block_size, AwSdHostState),
|
||||
VMSTATE_UINT32(byte_count, AwSdHostState),
|
||||
VMSTATE_UINT32(transfer_cnt, AwSdHostState),
|
||||
VMSTATE_UINT32(command, AwSdHostState),
|
||||
VMSTATE_UINT32(command_arg, AwSdHostState),
|
||||
VMSTATE_UINT32_ARRAY(response, AwSdHostState, 4),
|
||||
VMSTATE_UINT32(irq_mask, AwSdHostState),
|
||||
VMSTATE_UINT32(irq_status, AwSdHostState),
|
||||
VMSTATE_UINT32(status, AwSdHostState),
|
||||
VMSTATE_UINT32(fifo_wlevel, AwSdHostState),
|
||||
VMSTATE_UINT32(fifo_func_sel, AwSdHostState),
|
||||
VMSTATE_UINT32(debug_enable, AwSdHostState),
|
||||
VMSTATE_UINT32(auto12_arg, AwSdHostState),
|
||||
VMSTATE_UINT32(newtiming_set, AwSdHostState),
|
||||
VMSTATE_UINT32(newtiming_debug, AwSdHostState),
|
||||
VMSTATE_UINT32(hardware_rst, AwSdHostState),
|
||||
VMSTATE_UINT32(dmac, AwSdHostState),
|
||||
VMSTATE_UINT32(desc_base, AwSdHostState),
|
||||
VMSTATE_UINT32(dmac_status, AwSdHostState),
|
||||
VMSTATE_UINT32(dmac_irq, AwSdHostState),
|
||||
VMSTATE_UINT32(card_threshold, AwSdHostState),
|
||||
VMSTATE_UINT32(startbit_detect, AwSdHostState),
|
||||
VMSTATE_UINT32(response_crc, AwSdHostState),
|
||||
VMSTATE_UINT32_ARRAY(data_crc, AwSdHostState, 8),
|
||||
VMSTATE_UINT32(status_crc, AwSdHostState),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void allwinner_sdhost_init(Object *obj)
|
||||
{
|
||||
AwSdHostState *s = AW_SDHOST(obj);
|
||||
|
||||
qbus_create_inplace(&s->sdbus, sizeof(s->sdbus),
|
||||
TYPE_AW_SDHOST_BUS, DEVICE(s), "sd-bus");
|
||||
|
||||
memory_region_init_io(&s->iomem, obj, &allwinner_sdhost_ops, s,
|
||||
TYPE_AW_SDHOST, 4 * KiB);
|
||||
sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
|
||||
sysbus_init_irq(SYS_BUS_DEVICE(s), &s->irq);
|
||||
}
|
||||
|
||||
static void allwinner_sdhost_reset(DeviceState *dev)
|
||||
{
|
||||
AwSdHostState *s = AW_SDHOST(dev);
|
||||
|
||||
s->global_ctl = REG_SD_GCTL_RST;
|
||||
s->clock_ctl = REG_SD_CKCR_RST;
|
||||
s->timeout = REG_SD_TMOR_RST;
|
||||
s->bus_width = REG_SD_BWDR_RST;
|
||||
s->block_size = REG_SD_BKSR_RST;
|
||||
s->byte_count = REG_SD_BYCR_RST;
|
||||
s->transfer_cnt = 0;
|
||||
|
||||
s->command = REG_SD_CMDR_RST;
|
||||
s->command_arg = REG_SD_CAGR_RST;
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(s->response); i++) {
|
||||
s->response[i] = REG_SD_RESP_RST;
|
||||
}
|
||||
|
||||
s->irq_mask = REG_SD_IMKR_RST;
|
||||
s->irq_status = REG_SD_RISR_RST;
|
||||
s->status = REG_SD_STAR_RST;
|
||||
|
||||
s->fifo_wlevel = REG_SD_FWLR_RST;
|
||||
s->fifo_func_sel = REG_SD_FUNS_RST;
|
||||
s->debug_enable = REG_SD_DBGC_RST;
|
||||
s->auto12_arg = REG_SD_A12A_RST;
|
||||
s->newtiming_set = REG_SD_NTSR_RST;
|
||||
s->newtiming_debug = REG_SD_SDBG_RST;
|
||||
s->hardware_rst = REG_SD_HWRST_RST;
|
||||
s->dmac = REG_SD_DMAC_RST;
|
||||
s->desc_base = REG_SD_DLBA_RST;
|
||||
s->dmac_status = REG_SD_IDST_RST;
|
||||
s->dmac_irq = REG_SD_IDIE_RST;
|
||||
s->card_threshold = REG_SD_THLDC_RST;
|
||||
s->startbit_detect = REG_SD_DSBD_RST;
|
||||
s->response_crc = REG_SD_RES_CRC_RST;
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(s->data_crc); i++) {
|
||||
s->data_crc[i] = REG_SD_DATA_CRC_RST;
|
||||
}
|
||||
|
||||
s->status_crc = REG_SD_CRC_STA_RST;
|
||||
}
|
||||
|
||||
static void allwinner_sdhost_bus_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
SDBusClass *sbc = SD_BUS_CLASS(klass);
|
||||
|
||||
sbc->set_inserted = allwinner_sdhost_set_inserted;
|
||||
}
|
||||
|
||||
static void allwinner_sdhost_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->reset = allwinner_sdhost_reset;
|
||||
dc->vmsd = &vmstate_allwinner_sdhost;
|
||||
}
|
||||
|
||||
static void allwinner_sdhost_sun4i_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
AwSdHostClass *sc = AW_SDHOST_CLASS(klass);
|
||||
sc->max_desc_size = 8 * KiB;
|
||||
}
|
||||
|
||||
static void allwinner_sdhost_sun5i_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
AwSdHostClass *sc = AW_SDHOST_CLASS(klass);
|
||||
sc->max_desc_size = 64 * KiB;
|
||||
}
|
||||
|
||||
static TypeInfo allwinner_sdhost_info = {
|
||||
.name = TYPE_AW_SDHOST,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_init = allwinner_sdhost_init,
|
||||
.instance_size = sizeof(AwSdHostState),
|
||||
.class_init = allwinner_sdhost_class_init,
|
||||
.class_size = sizeof(AwSdHostClass),
|
||||
.abstract = true,
|
||||
};
|
||||
|
||||
static const TypeInfo allwinner_sdhost_sun4i_info = {
|
||||
.name = TYPE_AW_SDHOST_SUN4I,
|
||||
.parent = TYPE_AW_SDHOST,
|
||||
.class_init = allwinner_sdhost_sun4i_class_init,
|
||||
};
|
||||
|
||||
static const TypeInfo allwinner_sdhost_sun5i_info = {
|
||||
.name = TYPE_AW_SDHOST_SUN5I,
|
||||
.parent = TYPE_AW_SDHOST,
|
||||
.class_init = allwinner_sdhost_sun5i_class_init,
|
||||
};
|
||||
|
||||
static const TypeInfo allwinner_sdhost_bus_info = {
|
||||
.name = TYPE_AW_SDHOST_BUS,
|
||||
.parent = TYPE_SD_BUS,
|
||||
.instance_size = sizeof(SDBus),
|
||||
.class_init = allwinner_sdhost_bus_class_init,
|
||||
};
|
||||
|
||||
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_bus_info);
|
||||
}
|
||||
|
||||
type_init(allwinner_sdhost_register_types)
|
@ -1,5 +1,12 @@
|
||||
# See docs/devel/tracing.txt for syntax documentation.
|
||||
|
||||
# allwinner-sdhost.c
|
||||
allwinner_sdhost_set_inserted(bool inserted) "inserted %u"
|
||||
allwinner_sdhost_process_desc(uint64_t desc_addr, uint32_t desc_size, bool is_write, uint32_t max_bytes) "desc_addr 0x%" PRIx64 " desc_size %" PRIu32 " is_write %u max_bytes %" PRIu32
|
||||
allwinner_sdhost_read(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
|
||||
allwinner_sdhost_write(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %" PRIu32
|
||||
allwinner_sdhost_update_irq(uint32_t irq) "IRQ bits 0x%" PRIx32
|
||||
|
||||
# bcm2835_sdhost.c
|
||||
bcm2835_sdhost_read(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
|
||||
bcm2835_sdhost_write(uint64_t offset, uint64_t data, unsigned size) "offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "qapi/error.h"
|
||||
#include "exec/address-spaces.h"
|
||||
#include "qemu/units.h"
|
||||
#include "trace.h"
|
||||
|
||||
#include "hw/irq.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
@ -513,6 +514,8 @@ static void aspeed_smc_flash_set_segment(AspeedSMCState *s, int cs,
|
||||
|
||||
s->ctrl->reg_to_segment(s, new, &seg);
|
||||
|
||||
trace_aspeed_smc_flash_set_segment(cs, new, seg.addr, seg.addr + seg.size);
|
||||
|
||||
/* The start address of CS0 is read-only */
|
||||
if (cs == 0 && seg.addr != s->ctrl->flash_window_base) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
@ -636,27 +639,23 @@ static inline int aspeed_smc_flash_is_4byte(const AspeedSMCFlash *fl)
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool aspeed_smc_is_ce_stop_active(const AspeedSMCFlash *fl)
|
||||
static void aspeed_smc_flash_do_select(AspeedSMCFlash *fl, bool unselect)
|
||||
{
|
||||
const AspeedSMCState *s = fl->controller;
|
||||
AspeedSMCState *s = fl->controller;
|
||||
|
||||
return s->regs[s->r_ctrl0 + fl->id] & CTRL_CE_STOP_ACTIVE;
|
||||
trace_aspeed_smc_flash_select(fl->id, unselect ? "un" : "");
|
||||
|
||||
qemu_set_irq(s->cs_lines[fl->id], unselect);
|
||||
}
|
||||
|
||||
static void aspeed_smc_flash_select(AspeedSMCFlash *fl)
|
||||
{
|
||||
AspeedSMCState *s = fl->controller;
|
||||
|
||||
s->regs[s->r_ctrl0 + fl->id] &= ~CTRL_CE_STOP_ACTIVE;
|
||||
qemu_set_irq(s->cs_lines[fl->id], aspeed_smc_is_ce_stop_active(fl));
|
||||
aspeed_smc_flash_do_select(fl, false);
|
||||
}
|
||||
|
||||
static void aspeed_smc_flash_unselect(AspeedSMCFlash *fl)
|
||||
{
|
||||
AspeedSMCState *s = fl->controller;
|
||||
|
||||
s->regs[s->r_ctrl0 + fl->id] |= CTRL_CE_STOP_ACTIVE;
|
||||
qemu_set_irq(s->cs_lines[fl->id], aspeed_smc_is_ce_stop_active(fl));
|
||||
aspeed_smc_flash_do_select(fl, true);
|
||||
}
|
||||
|
||||
static uint32_t aspeed_smc_check_segment_addr(const AspeedSMCFlash *fl,
|
||||
@ -753,6 +752,8 @@ static uint64_t aspeed_smc_flash_read(void *opaque, hwaddr addr, unsigned size)
|
||||
__func__, aspeed_smc_flash_mode(fl));
|
||||
}
|
||||
|
||||
trace_aspeed_smc_flash_read(fl->id, addr, size, ret,
|
||||
aspeed_smc_flash_mode(fl));
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -808,6 +809,9 @@ static bool aspeed_smc_do_snoop(AspeedSMCFlash *fl, uint64_t data,
|
||||
AspeedSMCState *s = fl->controller;
|
||||
uint8_t addr_width = aspeed_smc_flash_is_4byte(fl) ? 4 : 3;
|
||||
|
||||
trace_aspeed_smc_do_snoop(fl->id, s->snoop_index, s->snoop_dummies,
|
||||
(uint8_t) data & 0xff);
|
||||
|
||||
if (s->snoop_index == SNOOP_OFF) {
|
||||
return false; /* Do nothing */
|
||||
|
||||
@ -858,6 +862,9 @@ static void aspeed_smc_flash_write(void *opaque, hwaddr addr, uint64_t data,
|
||||
AspeedSMCState *s = fl->controller;
|
||||
int i;
|
||||
|
||||
trace_aspeed_smc_flash_write(fl->id, addr, size, data,
|
||||
aspeed_smc_flash_mode(fl));
|
||||
|
||||
if (!aspeed_smc_is_writable(fl)) {
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "%s: flash is not writable at 0x%"
|
||||
HWADDR_PRIx "\n", __func__, addr);
|
||||
@ -900,13 +907,25 @@ static const MemoryRegionOps aspeed_smc_flash_ops = {
|
||||
},
|
||||
};
|
||||
|
||||
static void aspeed_smc_flash_update_cs(AspeedSMCFlash *fl)
|
||||
static void aspeed_smc_flash_update_ctrl(AspeedSMCFlash *fl, uint32_t value)
|
||||
{
|
||||
AspeedSMCState *s = fl->controller;
|
||||
bool unselect;
|
||||
|
||||
s->snoop_index = aspeed_smc_is_ce_stop_active(fl) ? SNOOP_OFF : SNOOP_START;
|
||||
/* User mode selects the CS, other modes unselect */
|
||||
unselect = (value & CTRL_CMD_MODE_MASK) != CTRL_USERMODE;
|
||||
|
||||
qemu_set_irq(s->cs_lines[fl->id], aspeed_smc_is_ce_stop_active(fl));
|
||||
/* A change of CTRL_CE_STOP_ACTIVE from 0 to 1, unselects the CS */
|
||||
if (!(s->regs[s->r_ctrl0 + fl->id] & CTRL_CE_STOP_ACTIVE) &&
|
||||
value & CTRL_CE_STOP_ACTIVE) {
|
||||
unselect = true;
|
||||
}
|
||||
|
||||
s->regs[s->r_ctrl0 + fl->id] = value;
|
||||
|
||||
s->snoop_index = unselect ? SNOOP_OFF : SNOOP_START;
|
||||
|
||||
aspeed_smc_flash_do_select(fl, unselect);
|
||||
}
|
||||
|
||||
static void aspeed_smc_reset(DeviceState *d)
|
||||
@ -972,6 +991,9 @@ static uint64_t aspeed_smc_read(void *opaque, hwaddr addr, unsigned int size)
|
||||
(s->ctrl->has_dma && addr == R_DMA_CHECKSUM) ||
|
||||
(addr >= R_SEG_ADDR0 && addr < R_SEG_ADDR0 + s->ctrl->max_slaves) ||
|
||||
(addr >= s->r_ctrl0 && addr < s->r_ctrl0 + s->ctrl->max_slaves)) {
|
||||
|
||||
trace_aspeed_smc_read(addr, size, s->regs[addr]);
|
||||
|
||||
return s->regs[addr];
|
||||
} else {
|
||||
qemu_log_mask(LOG_UNIMP, "%s: not implemented: 0x%" HWADDR_PRIx "\n",
|
||||
@ -1091,6 +1113,7 @@ static void aspeed_smc_dma_checksum(AspeedSMCState *s)
|
||||
__func__, s->regs[R_DMA_FLASH_ADDR]);
|
||||
return;
|
||||
}
|
||||
trace_aspeed_smc_dma_checksum(s->regs[R_DMA_FLASH_ADDR], data);
|
||||
|
||||
/*
|
||||
* When the DMA is on-going, the DMA registers are updated
|
||||
@ -1225,6 +1248,8 @@ static void aspeed_smc_write(void *opaque, hwaddr addr, uint64_t data,
|
||||
|
||||
addr >>= 2;
|
||||
|
||||
trace_aspeed_smc_write(addr, size, data);
|
||||
|
||||
if (addr == s->r_conf ||
|
||||
(addr >= s->r_timings &&
|
||||
addr < s->r_timings + s->ctrl->nregs_timings) ||
|
||||
@ -1232,8 +1257,7 @@ static void aspeed_smc_write(void *opaque, hwaddr addr, uint64_t data,
|
||||
s->regs[addr] = value;
|
||||
} else if (addr >= s->r_ctrl0 && addr < s->r_ctrl0 + s->num_cs) {
|
||||
int cs = addr - s->r_ctrl0;
|
||||
s->regs[addr] = value;
|
||||
aspeed_smc_flash_update_cs(&s->flashes[cs]);
|
||||
aspeed_smc_flash_update_ctrl(&s->flashes[cs], value);
|
||||
} else if (addr >= R_SEG_ADDR0 &&
|
||||
addr < R_SEG_ADDR0 + s->ctrl->max_slaves) {
|
||||
int cs = addr - R_SEG_ADDR0;
|
||||
|
10
hw/ssi/trace-events
Normal file
10
hw/ssi/trace-events
Normal file
@ -0,0 +1,10 @@
|
||||
# aspeed_smc.c
|
||||
|
||||
aspeed_smc_flash_set_segment(int cs, uint64_t reg, uint64_t start, uint64_t end) "CS%d segreg=0x%"PRIx64" [ 0x%"PRIx64" - 0x%"PRIx64" ]"
|
||||
aspeed_smc_flash_read(int cs, uint64_t addr, uint32_t size, uint64_t data, int mode) "CS%d @0x%" PRIx64 " size %u: 0x%" PRIx64" mode:%d"
|
||||
aspeed_smc_do_snoop(int cs, int index, int dummies, int data) "CS%d index:0x%x dummies:%d data:0x%x"
|
||||
aspeed_smc_flash_write(int cs, uint64_t addr, uint32_t size, uint64_t data, int mode) "CS%d @0x%" PRIx64 " size %u: 0x%" PRIx64" mode:%d"
|
||||
aspeed_smc_read(uint64_t addr, uint32_t size, uint64_t data) "@0x%" PRIx64 " size %u: 0x%" PRIx64
|
||||
aspeed_smc_dma_checksum(uint32_t addr, uint32_t data) "0x%08x: 0x%08x"
|
||||
aspeed_smc_write(uint64_t addr, uint32_t size, uint64_t data) "@0x%" PRIx64 " size %u: 0x%" PRIx64
|
||||
aspeed_smc_flash_select(int cs, const char *prefix) "CS%d %sselect"
|
@ -131,6 +131,22 @@ static const TypeInfo ehci_exynos4210_type_info = {
|
||||
.class_init = ehci_exynos4210_class_init,
|
||||
};
|
||||
|
||||
static void ehci_aw_h3_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(oc);
|
||||
DeviceClass *dc = DEVICE_CLASS(oc);
|
||||
|
||||
sec->capsbase = 0x0;
|
||||
sec->opregbase = 0x10;
|
||||
set_bit(DEVICE_CATEGORY_USB, dc->categories);
|
||||
}
|
||||
|
||||
static const TypeInfo ehci_aw_h3_type_info = {
|
||||
.name = TYPE_AW_H3_EHCI,
|
||||
.parent = TYPE_SYS_BUS_EHCI,
|
||||
.class_init = ehci_aw_h3_class_init,
|
||||
};
|
||||
|
||||
static void ehci_tegra2_class_init(ObjectClass *oc, void *data)
|
||||
{
|
||||
SysBusEHCIClass *sec = SYS_BUS_EHCI_CLASS(oc);
|
||||
@ -252,6 +268,7 @@ static void ehci_sysbus_register_types(void)
|
||||
type_register_static(&ehci_type_info);
|
||||
type_register_static(&ehci_platform_type_info);
|
||||
type_register_static(&ehci_exynos4210_type_info);
|
||||
type_register_static(&ehci_aw_h3_type_info);
|
||||
type_register_static(&ehci_tegra2_type_info);
|
||||
type_register_static(&ehci_ppc4xx_type_info);
|
||||
type_register_static(&ehci_fusbh200_type_info);
|
||||
|
@ -342,6 +342,7 @@ typedef struct EHCIPCIState {
|
||||
#define TYPE_SYS_BUS_EHCI "sysbus-ehci-usb"
|
||||
#define TYPE_PLATFORM_EHCI "platform-ehci-usb"
|
||||
#define TYPE_EXYNOS4210_EHCI "exynos4210-ehci-usb"
|
||||
#define TYPE_AW_H3_EHCI "aw-h3-ehci-usb"
|
||||
#define TYPE_TEGRA2_EHCI "tegra2-ehci-usb"
|
||||
#define TYPE_PPC4xx_EHCI "ppc4xx-ehci-usb"
|
||||
#define TYPE_FUSBH200_EHCI "fusbh200-ehci-usb"
|
||||
|
@ -7,9 +7,11 @@
|
||||
#include "hw/timer/allwinner-a10-pit.h"
|
||||
#include "hw/intc/allwinner-a10-pic.h"
|
||||
#include "hw/net/allwinner_emac.h"
|
||||
#include "hw/sd/allwinner-sdhost.h"
|
||||
#include "hw/ide/ahci.h"
|
||||
#include "hw/usb/hcd-ohci.h"
|
||||
#include "hw/usb/hcd-ehci.h"
|
||||
#include "hw/rtc/allwinner-rtc.h"
|
||||
|
||||
#include "target/arm/cpu.h"
|
||||
|
||||
@ -31,6 +33,8 @@ typedef struct AwA10State {
|
||||
AwA10PICState intc;
|
||||
AwEmacState emac;
|
||||
AllwinnerAHCIState sata;
|
||||
AwSdHostState mmc0;
|
||||
AwRtcState rtc;
|
||||
MemoryRegion sram_a;
|
||||
EHCISysBusState ehci[AW_A10_NUM_USB];
|
||||
OHCISysBusState ohci[AW_A10_NUM_USB];
|
||||
|
161
include/hw/arm/allwinner-h3.h
Normal file
161
include/hw/arm/allwinner-h3.h
Normal file
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Allwinner H3 System on Chip emulation
|
||||
*
|
||||
* Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The Allwinner H3 is a System on Chip containing four ARM Cortex A7
|
||||
* processor cores. Features and specifications include DDR2/DDR3 memory,
|
||||
* SD/MMC storage cards, 10/100/1000Mbit Ethernet, USB 2.0, HDMI and
|
||||
* various I/O modules.
|
||||
*
|
||||
* This implementation is based on the following datasheet:
|
||||
*
|
||||
* https://linux-sunxi.org/File:Allwinner_H3_Datasheet_V1.2.pdf
|
||||
*
|
||||
* The latest datasheet and more info can be found on the Linux Sunxi wiki:
|
||||
*
|
||||
* https://linux-sunxi.org/H3
|
||||
*/
|
||||
|
||||
#ifndef HW_ARM_ALLWINNER_H3_H
|
||||
#define HW_ARM_ALLWINNER_H3_H
|
||||
|
||||
#include "qom/object.h"
|
||||
#include "hw/arm/boot.h"
|
||||
#include "hw/timer/allwinner-a10-pit.h"
|
||||
#include "hw/intc/arm_gic.h"
|
||||
#include "hw/misc/allwinner-h3-ccu.h"
|
||||
#include "hw/misc/allwinner-cpucfg.h"
|
||||
#include "hw/misc/allwinner-h3-dramc.h"
|
||||
#include "hw/misc/allwinner-h3-sysctrl.h"
|
||||
#include "hw/misc/allwinner-sid.h"
|
||||
#include "hw/sd/allwinner-sdhost.h"
|
||||
#include "hw/net/allwinner-sun8i-emac.h"
|
||||
#include "hw/rtc/allwinner-rtc.h"
|
||||
#include "target/arm/cpu.h"
|
||||
#include "sysemu/block-backend.h"
|
||||
|
||||
/**
|
||||
* Allwinner H3 device list
|
||||
*
|
||||
* This enumeration is can be used refer to a particular device in the
|
||||
* Allwinner H3 SoC. For example, the physical memory base address for
|
||||
* each device can be found in the AwH3State object in the memmap member
|
||||
* using the device enum value as index.
|
||||
*
|
||||
* @see AwH3State
|
||||
*/
|
||||
enum {
|
||||
AW_H3_SRAM_A1,
|
||||
AW_H3_SRAM_A2,
|
||||
AW_H3_SRAM_C,
|
||||
AW_H3_SYSCTRL,
|
||||
AW_H3_MMC0,
|
||||
AW_H3_SID,
|
||||
AW_H3_EHCI0,
|
||||
AW_H3_OHCI0,
|
||||
AW_H3_EHCI1,
|
||||
AW_H3_OHCI1,
|
||||
AW_H3_EHCI2,
|
||||
AW_H3_OHCI2,
|
||||
AW_H3_EHCI3,
|
||||
AW_H3_OHCI3,
|
||||
AW_H3_CCU,
|
||||
AW_H3_PIT,
|
||||
AW_H3_UART0,
|
||||
AW_H3_UART1,
|
||||
AW_H3_UART2,
|
||||
AW_H3_UART3,
|
||||
AW_H3_EMAC,
|
||||
AW_H3_DRAMCOM,
|
||||
AW_H3_DRAMCTL,
|
||||
AW_H3_DRAMPHY,
|
||||
AW_H3_GIC_DIST,
|
||||
AW_H3_GIC_CPU,
|
||||
AW_H3_GIC_HYP,
|
||||
AW_H3_GIC_VCPU,
|
||||
AW_H3_RTC,
|
||||
AW_H3_CPUCFG,
|
||||
AW_H3_SDRAM
|
||||
};
|
||||
|
||||
/** Total number of CPU cores in the H3 SoC */
|
||||
#define AW_H3_NUM_CPUS (4)
|
||||
|
||||
/**
|
||||
* Allwinner H3 object model
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Object type for the Allwinner H3 SoC */
|
||||
#define TYPE_AW_H3 "allwinner-h3"
|
||||
|
||||
/** Convert input object to Allwinner H3 state object */
|
||||
#define AW_H3(obj) OBJECT_CHECK(AwH3State, (obj), TYPE_AW_H3)
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Allwinner H3 object
|
||||
*
|
||||
* This struct contains the state of all the devices
|
||||
* which are currently emulated by the H3 SoC code.
|
||||
*/
|
||||
typedef struct AwH3State {
|
||||
/*< private >*/
|
||||
DeviceState parent_obj;
|
||||
/*< public >*/
|
||||
|
||||
ARMCPU cpus[AW_H3_NUM_CPUS];
|
||||
const hwaddr *memmap;
|
||||
AwA10PITState timer;
|
||||
AwH3ClockCtlState ccu;
|
||||
AwCpuCfgState cpucfg;
|
||||
AwH3DramCtlState dramc;
|
||||
AwH3SysCtrlState sysctrl;
|
||||
AwSidState sid;
|
||||
AwSdHostState mmc0;
|
||||
AwSun8iEmacState emac;
|
||||
AwRtcState rtc;
|
||||
GICState gic;
|
||||
MemoryRegion sram_a1;
|
||||
MemoryRegion sram_a2;
|
||||
MemoryRegion sram_c;
|
||||
} AwH3State;
|
||||
|
||||
/**
|
||||
* Emulate Boot ROM firmware setup functionality.
|
||||
*
|
||||
* A real Allwinner H3 SoC contains a Boot ROM
|
||||
* which is the first code that runs right after
|
||||
* the SoC is powered on. The Boot ROM is responsible
|
||||
* for loading user code (e.g. a bootloader) from any
|
||||
* of the supported external devices and writing the
|
||||
* downloaded code to internal SRAM. After loading the SoC
|
||||
* begins executing the code written to SRAM.
|
||||
*
|
||||
* This function emulates the Boot ROM by copying 32 KiB
|
||||
* of data from the given block device and writes it to
|
||||
* the start of the first internal SRAM memory.
|
||||
*
|
||||
* @s: Allwinner H3 state object pointer
|
||||
* @blk: Block backend device object pointer
|
||||
*/
|
||||
void allwinner_h3_bootrom_setup(AwH3State *s, BlockBackend *blk);
|
||||
|
||||
#endif /* HW_ARM_ALLWINNER_H3_H */
|
@ -27,6 +27,8 @@
|
||||
#include "hw/misc/imx_rngc.h"
|
||||
#include "hw/i2c/imx_i2c.h"
|
||||
#include "hw/gpio/imx_gpio.h"
|
||||
#include "hw/sd/sdhci.h"
|
||||
#include "hw/usb/chipidea.h"
|
||||
#include "exec/memory.h"
|
||||
#include "target/arm/cpu.h"
|
||||
|
||||
@ -38,6 +40,8 @@
|
||||
#define FSL_IMX25_NUM_EPITS 2
|
||||
#define FSL_IMX25_NUM_I2CS 3
|
||||
#define FSL_IMX25_NUM_GPIOS 4
|
||||
#define FSL_IMX25_NUM_ESDHCS 2
|
||||
#define FSL_IMX25_NUM_USBS 2
|
||||
|
||||
typedef struct FslIMX25State {
|
||||
/*< private >*/
|
||||
@ -54,6 +58,8 @@ typedef struct FslIMX25State {
|
||||
IMXRNGCState rngc;
|
||||
IMXI2CState i2c[FSL_IMX25_NUM_I2CS];
|
||||
IMXGPIOState gpio[FSL_IMX25_NUM_GPIOS];
|
||||
SDHCIState esdhc[FSL_IMX25_NUM_ESDHCS];
|
||||
ChipideaState usb[FSL_IMX25_NUM_USBS];
|
||||
MemoryRegion rom[2];
|
||||
MemoryRegion iram;
|
||||
MemoryRegion iram_alias;
|
||||
@ -215,10 +221,18 @@ typedef struct FslIMX25State {
|
||||
#define FSL_IMX25_GPIO3_SIZE 0x4000
|
||||
#define FSL_IMX25_RNGC_ADDR 0x53FB0000
|
||||
#define FSL_IMX25_RNGC_SIZE 0x4000
|
||||
#define FSL_IMX25_ESDHC1_ADDR 0x53FB4000
|
||||
#define FSL_IMX25_ESDHC1_SIZE 0x4000
|
||||
#define FSL_IMX25_ESDHC2_ADDR 0x53FB8000
|
||||
#define FSL_IMX25_ESDHC2_SIZE 0x4000
|
||||
#define FSL_IMX25_GPIO1_ADDR 0x53FCC000
|
||||
#define FSL_IMX25_GPIO1_SIZE 0x4000
|
||||
#define FSL_IMX25_GPIO2_ADDR 0x53FD0000
|
||||
#define FSL_IMX25_GPIO2_SIZE 0x4000
|
||||
#define FSL_IMX25_USB1_ADDR 0x53FF4000
|
||||
#define FSL_IMX25_USB1_SIZE 0x0200
|
||||
#define FSL_IMX25_USB2_ADDR 0x53FF4400
|
||||
#define FSL_IMX25_USB2_SIZE 0x0200
|
||||
#define FSL_IMX25_AVIC_ADDR 0x68000000
|
||||
#define FSL_IMX25_AVIC_SIZE 0x4000
|
||||
#define FSL_IMX25_IRAM_ADDR 0x78000000
|
||||
@ -250,5 +264,9 @@ typedef struct FslIMX25State {
|
||||
#define FSL_IMX25_GPIO2_IRQ 51
|
||||
#define FSL_IMX25_GPIO3_IRQ 16
|
||||
#define FSL_IMX25_GPIO4_IRQ 23
|
||||
#define FSL_IMX25_ESDHC1_IRQ 9
|
||||
#define FSL_IMX25_ESDHC2_IRQ 8
|
||||
#define FSL_IMX25_USB1_IRQ 37
|
||||
#define FSL_IMX25_USB2_IRQ 35
|
||||
|
||||
#endif /* FSL_IMX25_H */
|
||||
|
@ -95,6 +95,14 @@ typedef enum VirtIOMMUType {
|
||||
VIRT_IOMMU_VIRTIO,
|
||||
} VirtIOMMUType;
|
||||
|
||||
typedef enum VirtGICType {
|
||||
VIRT_GIC_VERSION_MAX,
|
||||
VIRT_GIC_VERSION_HOST,
|
||||
VIRT_GIC_VERSION_2,
|
||||
VIRT_GIC_VERSION_3,
|
||||
VIRT_GIC_VERSION_NOSEL,
|
||||
} VirtGICType;
|
||||
|
||||
typedef struct MemMapEntry {
|
||||
hwaddr base;
|
||||
hwaddr size;
|
||||
@ -123,7 +131,7 @@ typedef struct {
|
||||
bool highmem_ecam;
|
||||
bool its;
|
||||
bool virt;
|
||||
int32_t gic_version;
|
||||
VirtGICType gic_version;
|
||||
VirtIOMMUType iommu;
|
||||
uint16_t virtio_iommu_bdf;
|
||||
struct arm_boot_info bootinfo;
|
||||
@ -162,7 +170,7 @@ static inline int virt_gicv3_redist_region_count(VirtMachineState *vms)
|
||||
uint32_t redist0_capacity =
|
||||
vms->memmap[VIRT_GIC_REDIST].size / GICV3_REDIST_SIZE;
|
||||
|
||||
assert(vms->gic_version == 3);
|
||||
assert(vms->gic_version == VIRT_GIC_VERSION_3);
|
||||
|
||||
return vms->smp_cpus > redist0_capacity ? 2 : 1;
|
||||
}
|
||||
|
52
include/hw/misc/allwinner-cpucfg.h
Normal file
52
include/hw/misc/allwinner-cpucfg.h
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Allwinner CPU Configuration Module emulation
|
||||
*
|
||||
* Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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/>.
|
||||
*/
|
||||
|
||||
#ifndef HW_MISC_ALLWINNER_CPUCFG_H
|
||||
#define HW_MISC_ALLWINNER_CPUCFG_H
|
||||
|
||||
#include "qom/object.h"
|
||||
#include "hw/sysbus.h"
|
||||
|
||||
/**
|
||||
* Object model
|
||||
* @{
|
||||
*/
|
||||
|
||||
#define TYPE_AW_CPUCFG "allwinner-cpucfg"
|
||||
#define AW_CPUCFG(obj) \
|
||||
OBJECT_CHECK(AwCpuCfgState, (obj), TYPE_AW_CPUCFG)
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Allwinner CPU Configuration Module instance state
|
||||
*/
|
||||
typedef struct AwCpuCfgState {
|
||||
/*< private >*/
|
||||
SysBusDevice parent_obj;
|
||||
/*< public >*/
|
||||
|
||||
MemoryRegion iomem;
|
||||
uint32_t gen_ctrl;
|
||||
uint32_t super_standby;
|
||||
uint32_t entry_addr;
|
||||
|
||||
} AwCpuCfgState;
|
||||
|
||||
#endif /* HW_MISC_ALLWINNER_CPUCFG_H */
|
66
include/hw/misc/allwinner-h3-ccu.h
Normal file
66
include/hw/misc/allwinner-h3-ccu.h
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Allwinner H3 Clock Control Unit emulation
|
||||
*
|
||||
* Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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/>.
|
||||
*/
|
||||
|
||||
#ifndef HW_MISC_ALLWINNER_H3_CCU_H
|
||||
#define HW_MISC_ALLWINNER_H3_CCU_H
|
||||
|
||||
#include "qom/object.h"
|
||||
#include "hw/sysbus.h"
|
||||
|
||||
/**
|
||||
* @name Constants
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Size of register I/O address space used by CCU device */
|
||||
#define AW_H3_CCU_IOSIZE (0x400)
|
||||
|
||||
/** Total number of known registers */
|
||||
#define AW_H3_CCU_REGS_NUM (AW_H3_CCU_IOSIZE / sizeof(uint32_t))
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Object model
|
||||
* @{
|
||||
*/
|
||||
|
||||
#define TYPE_AW_H3_CCU "allwinner-h3-ccu"
|
||||
#define AW_H3_CCU(obj) \
|
||||
OBJECT_CHECK(AwH3ClockCtlState, (obj), TYPE_AW_H3_CCU)
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Allwinner H3 CCU object instance state.
|
||||
*/
|
||||
typedef struct AwH3ClockCtlState {
|
||||
/*< private >*/
|
||||
SysBusDevice parent_obj;
|
||||
/*< public >*/
|
||||
|
||||
/** Maps I/O registers in physical memory */
|
||||
MemoryRegion iomem;
|
||||
|
||||
/** Array of hardware registers */
|
||||
uint32_t regs[AW_H3_CCU_REGS_NUM];
|
||||
|
||||
} AwH3ClockCtlState;
|
||||
|
||||
#endif /* HW_MISC_ALLWINNER_H3_CCU_H */
|
106
include/hw/misc/allwinner-h3-dramc.h
Normal file
106
include/hw/misc/allwinner-h3-dramc.h
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Allwinner H3 SDRAM Controller emulation
|
||||
*
|
||||
* Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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/>.
|
||||
*/
|
||||
|
||||
#ifndef HW_MISC_ALLWINNER_H3_DRAMC_H
|
||||
#define HW_MISC_ALLWINNER_H3_DRAMC_H
|
||||
|
||||
#include "qom/object.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "exec/hwaddr.h"
|
||||
|
||||
/**
|
||||
* Constants
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Highest register address used by DRAMCOM module */
|
||||
#define AW_H3_DRAMCOM_REGS_MAXADDR (0x804)
|
||||
|
||||
/** Total number of known DRAMCOM registers */
|
||||
#define AW_H3_DRAMCOM_REGS_NUM (AW_H3_DRAMCOM_REGS_MAXADDR / \
|
||||
sizeof(uint32_t))
|
||||
|
||||
/** Highest register address used by DRAMCTL module */
|
||||
#define AW_H3_DRAMCTL_REGS_MAXADDR (0x88c)
|
||||
|
||||
/** Total number of known DRAMCTL registers */
|
||||
#define AW_H3_DRAMCTL_REGS_NUM (AW_H3_DRAMCTL_REGS_MAXADDR / \
|
||||
sizeof(uint32_t))
|
||||
|
||||
/** Highest register address used by DRAMPHY module */
|
||||
#define AW_H3_DRAMPHY_REGS_MAXADDR (0x4)
|
||||
|
||||
/** Total number of known DRAMPHY registers */
|
||||
#define AW_H3_DRAMPHY_REGS_NUM (AW_H3_DRAMPHY_REGS_MAXADDR / \
|
||||
sizeof(uint32_t))
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Object model
|
||||
* @{
|
||||
*/
|
||||
|
||||
#define TYPE_AW_H3_DRAMC "allwinner-h3-dramc"
|
||||
#define AW_H3_DRAMC(obj) \
|
||||
OBJECT_CHECK(AwH3DramCtlState, (obj), TYPE_AW_H3_DRAMC)
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Allwinner H3 SDRAM Controller object instance state.
|
||||
*/
|
||||
typedef struct AwH3DramCtlState {
|
||||
/*< private >*/
|
||||
SysBusDevice parent_obj;
|
||||
/*< public >*/
|
||||
|
||||
/** Physical base address for start of RAM */
|
||||
hwaddr ram_addr;
|
||||
|
||||
/** Total RAM size in megabytes */
|
||||
uint32_t ram_size;
|
||||
|
||||
/**
|
||||
* @name Memory Regions
|
||||
* @{
|
||||
*/
|
||||
|
||||
MemoryRegion row_mirror; /**< Simulates rows for RAM size detection */
|
||||
MemoryRegion row_mirror_alias; /**< Alias of the row which is mirrored */
|
||||
MemoryRegion dramcom_iomem; /**< DRAMCOM module I/O registers */
|
||||
MemoryRegion dramctl_iomem; /**< DRAMCTL module I/O registers */
|
||||
MemoryRegion dramphy_iomem; /**< DRAMPHY module I/O registers */
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Hardware Registers
|
||||
* @{
|
||||
*/
|
||||
|
||||
uint32_t dramcom[AW_H3_DRAMCOM_REGS_NUM]; /**< Array of DRAMCOM registers */
|
||||
uint32_t dramctl[AW_H3_DRAMCTL_REGS_NUM]; /**< Array of DRAMCTL registers */
|
||||
uint32_t dramphy[AW_H3_DRAMPHY_REGS_NUM] ;/**< Array of DRAMPHY registers */
|
||||
|
||||
/** @} */
|
||||
|
||||
} AwH3DramCtlState;
|
||||
|
||||
#endif /* HW_MISC_ALLWINNER_H3_DRAMC_H */
|
67
include/hw/misc/allwinner-h3-sysctrl.h
Normal file
67
include/hw/misc/allwinner-h3-sysctrl.h
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Allwinner H3 System Control emulation
|
||||
*
|
||||
* Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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/>.
|
||||
*/
|
||||
|
||||
#ifndef HW_MISC_ALLWINNER_H3_SYSCTRL_H
|
||||
#define HW_MISC_ALLWINNER_H3_SYSCTRL_H
|
||||
|
||||
#include "qom/object.h"
|
||||
#include "hw/sysbus.h"
|
||||
|
||||
/**
|
||||
* @name Constants
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Highest register address used by System Control device */
|
||||
#define AW_H3_SYSCTRL_REGS_MAXADDR (0x30)
|
||||
|
||||
/** Total number of known registers */
|
||||
#define AW_H3_SYSCTRL_REGS_NUM ((AW_H3_SYSCTRL_REGS_MAXADDR / \
|
||||
sizeof(uint32_t)) + 1)
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Object model
|
||||
* @{
|
||||
*/
|
||||
|
||||
#define TYPE_AW_H3_SYSCTRL "allwinner-h3-sysctrl"
|
||||
#define AW_H3_SYSCTRL(obj) \
|
||||
OBJECT_CHECK(AwH3SysCtrlState, (obj), TYPE_AW_H3_SYSCTRL)
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Allwinner H3 System Control object instance state
|
||||
*/
|
||||
typedef struct AwH3SysCtrlState {
|
||||
/*< private >*/
|
||||
SysBusDevice parent_obj;
|
||||
/*< public >*/
|
||||
|
||||
/** Maps I/O registers in physical memory */
|
||||
MemoryRegion iomem;
|
||||
|
||||
/** Array of hardware registers */
|
||||
uint32_t regs[AW_H3_SYSCTRL_REGS_NUM];
|
||||
|
||||
} AwH3SysCtrlState;
|
||||
|
||||
#endif /* HW_MISC_ALLWINNER_H3_SYSCTRL_H */
|
60
include/hw/misc/allwinner-sid.h
Normal file
60
include/hw/misc/allwinner-sid.h
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Allwinner Security ID emulation
|
||||
*
|
||||
* Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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/>.
|
||||
*/
|
||||
|
||||
#ifndef HW_MISC_ALLWINNER_SID_H
|
||||
#define HW_MISC_ALLWINNER_SID_H
|
||||
|
||||
#include "qom/object.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "qemu/uuid.h"
|
||||
|
||||
/**
|
||||
* Object model
|
||||
* @{
|
||||
*/
|
||||
|
||||
#define TYPE_AW_SID "allwinner-sid"
|
||||
#define AW_SID(obj) \
|
||||
OBJECT_CHECK(AwSidState, (obj), TYPE_AW_SID)
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Allwinner Security ID object instance state
|
||||
*/
|
||||
typedef struct AwSidState {
|
||||
/*< private >*/
|
||||
SysBusDevice parent_obj;
|
||||
/*< public >*/
|
||||
|
||||
/** Maps I/O registers in physical memory */
|
||||
MemoryRegion iomem;
|
||||
|
||||
/** Control register defines how and what to read */
|
||||
uint32_t control;
|
||||
|
||||
/** RdKey register contains the data retrieved by the device */
|
||||
uint32_t rdkey;
|
||||
|
||||
/** Stores the emulated device identifier */
|
||||
QemuUUID identifier;
|
||||
|
||||
} AwSidState;
|
||||
|
||||
#endif /* HW_MISC_ALLWINNER_SID_H */
|
99
include/hw/net/allwinner-sun8i-emac.h
Normal file
99
include/hw/net/allwinner-sun8i-emac.h
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Allwinner Sun8i Ethernet MAC emulation
|
||||
*
|
||||
* Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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/>.
|
||||
*/
|
||||
|
||||
#ifndef HW_NET_ALLWINNER_SUN8I_EMAC_H
|
||||
#define HW_NET_ALLWINNER_SUN8I_EMAC_H
|
||||
|
||||
#include "qom/object.h"
|
||||
#include "net/net.h"
|
||||
#include "hw/sysbus.h"
|
||||
|
||||
/**
|
||||
* Object model
|
||||
* @{
|
||||
*/
|
||||
|
||||
#define TYPE_AW_SUN8I_EMAC "allwinner-sun8i-emac"
|
||||
#define AW_SUN8I_EMAC(obj) \
|
||||
OBJECT_CHECK(AwSun8iEmacState, (obj), TYPE_AW_SUN8I_EMAC)
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Allwinner Sun8i EMAC object instance state
|
||||
*/
|
||||
typedef struct AwSun8iEmacState {
|
||||
/*< private >*/
|
||||
SysBusDevice parent_obj;
|
||||
/*< public >*/
|
||||
|
||||
/** Maps I/O registers in physical memory */
|
||||
MemoryRegion iomem;
|
||||
|
||||
/** Interrupt output signal to notify CPU */
|
||||
qemu_irq irq;
|
||||
|
||||
/** Generic Network Interface Controller (NIC) for networking API */
|
||||
NICState *nic;
|
||||
|
||||
/** Generic Network Interface Controller (NIC) configuration */
|
||||
NICConf conf;
|
||||
|
||||
/**
|
||||
* @name Media Independent Interface (MII)
|
||||
* @{
|
||||
*/
|
||||
|
||||
uint8_t mii_phy_addr; /**< PHY address */
|
||||
uint32_t mii_cr; /**< Control */
|
||||
uint32_t mii_st; /**< Status */
|
||||
uint32_t mii_adv; /**< Advertised Abilities */
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Hardware Registers
|
||||
* @{
|
||||
*/
|
||||
|
||||
uint32_t basic_ctl0; /**< Basic Control 0 */
|
||||
uint32_t basic_ctl1; /**< Basic Control 1 */
|
||||
uint32_t int_en; /**< Interrupt Enable */
|
||||
uint32_t int_sta; /**< Interrupt Status */
|
||||
uint32_t frm_flt; /**< Receive Frame Filter */
|
||||
|
||||
uint32_t rx_ctl0; /**< Receive Control 0 */
|
||||
uint32_t rx_ctl1; /**< Receive Control 1 */
|
||||
uint32_t rx_desc_head; /**< Receive Descriptor List Address */
|
||||
uint32_t rx_desc_curr; /**< Current Receive Descriptor Address */
|
||||
|
||||
uint32_t tx_ctl0; /**< Transmit Control 0 */
|
||||
uint32_t tx_ctl1; /**< Transmit Control 1 */
|
||||
uint32_t tx_desc_head; /**< Transmit Descriptor List Address */
|
||||
uint32_t tx_desc_curr; /**< Current Transmit Descriptor Address */
|
||||
uint32_t tx_flowctl; /**< Transmit Flow Control */
|
||||
|
||||
uint32_t mii_cmd; /**< Management Interface Command */
|
||||
uint32_t mii_data; /**< Management Interface Data */
|
||||
|
||||
/** @} */
|
||||
|
||||
} AwSun8iEmacState;
|
||||
|
||||
#endif /* HW_NET_ALLWINNER_SUN8I_H */
|
134
include/hw/rtc/allwinner-rtc.h
Normal file
134
include/hw/rtc/allwinner-rtc.h
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Allwinner Real Time Clock emulation
|
||||
*
|
||||
* Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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/>.
|
||||
*/
|
||||
|
||||
#ifndef HW_MISC_ALLWINNER_RTC_H
|
||||
#define HW_MISC_ALLWINNER_RTC_H
|
||||
|
||||
#include "qom/object.h"
|
||||
#include "hw/sysbus.h"
|
||||
|
||||
/**
|
||||
* Constants
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Highest register address used by RTC device */
|
||||
#define AW_RTC_REGS_MAXADDR (0x200)
|
||||
|
||||
/** Total number of known registers */
|
||||
#define AW_RTC_REGS_NUM (AW_RTC_REGS_MAXADDR / sizeof(uint32_t))
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Object model types
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Generic Allwinner RTC device (abstract) */
|
||||
#define TYPE_AW_RTC "allwinner-rtc"
|
||||
|
||||
/** Allwinner RTC sun4i family (A10, A12) */
|
||||
#define TYPE_AW_RTC_SUN4I TYPE_AW_RTC "-sun4i"
|
||||
|
||||
/** Allwinner RTC sun6i family and newer (A31, H2+, H3, etc) */
|
||||
#define TYPE_AW_RTC_SUN6I TYPE_AW_RTC "-sun6i"
|
||||
|
||||
/** Allwinner RTC sun7i family (A20) */
|
||||
#define TYPE_AW_RTC_SUN7I TYPE_AW_RTC "-sun7i"
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Object model macros
|
||||
* @{
|
||||
*/
|
||||
|
||||
#define AW_RTC(obj) \
|
||||
OBJECT_CHECK(AwRtcState, (obj), TYPE_AW_RTC)
|
||||
#define AW_RTC_CLASS(klass) \
|
||||
OBJECT_CLASS_CHECK(AwRtcClass, (klass), TYPE_AW_RTC)
|
||||
#define AW_RTC_GET_CLASS(obj) \
|
||||
OBJECT_GET_CLASS(AwRtcClass, (obj), TYPE_AW_RTC)
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Allwinner RTC per-object instance state.
|
||||
*/
|
||||
typedef struct AwRtcState {
|
||||
/*< private >*/
|
||||
SysBusDevice parent_obj;
|
||||
/*< public >*/
|
||||
|
||||
/**
|
||||
* Actual year represented by the device when year counter is zero
|
||||
*
|
||||
* Can be overridden by the user using the corresponding 'base-year'
|
||||
* property. The base year used by the target OS driver can vary, for
|
||||
* example the Linux driver for sun6i uses 1970 while NetBSD uses 2000.
|
||||
*/
|
||||
int base_year;
|
||||
|
||||
/** Maps I/O registers in physical memory */
|
||||
MemoryRegion iomem;
|
||||
|
||||
/** Array of hardware registers */
|
||||
uint32_t regs[AW_RTC_REGS_NUM];
|
||||
|
||||
} AwRtcState;
|
||||
|
||||
/**
|
||||
* Allwinner RTC class-level struct.
|
||||
*
|
||||
* This struct is filled by each sunxi device specific code
|
||||
* such that the generic code can use this struct to support
|
||||
* all devices.
|
||||
*/
|
||||
typedef struct AwRtcClass {
|
||||
/*< private >*/
|
||||
SysBusDeviceClass parent_class;
|
||||
/*< public >*/
|
||||
|
||||
/** Defines device specific register map */
|
||||
const uint8_t *regmap;
|
||||
|
||||
/** Size of the regmap in bytes */
|
||||
size_t regmap_size;
|
||||
|
||||
/**
|
||||
* Read device specific register
|
||||
*
|
||||
* @offset: register offset to read
|
||||
* @return true if register read successful, false otherwise
|
||||
*/
|
||||
bool (*read)(AwRtcState *s, uint32_t offset);
|
||||
|
||||
/**
|
||||
* Write device specific register
|
||||
*
|
||||
* @offset: register offset to write
|
||||
* @data: value to set in register
|
||||
* @return true if register write successful, false otherwise
|
||||
*/
|
||||
bool (*write)(AwRtcState *s, uint32_t offset, uint32_t data);
|
||||
|
||||
} AwRtcClass;
|
||||
|
||||
#endif /* HW_MISC_ALLWINNER_RTC_H */
|
135
include/hw/sd/allwinner-sdhost.h
Normal file
135
include/hw/sd/allwinner-sdhost.h
Normal file
@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Allwinner (sun4i and above) SD Host Controller emulation
|
||||
*
|
||||
* Copyright (C) 2019 Niek Linnenbank <nieklinnenbank@gmail.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/>.
|
||||
*/
|
||||
|
||||
#ifndef HW_SD_ALLWINNER_SDHOST_H
|
||||
#define HW_SD_ALLWINNER_SDHOST_H
|
||||
|
||||
#include "qom/object.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "hw/sd/sd.h"
|
||||
|
||||
/**
|
||||
* Object model types
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Generic Allwinner SD Host Controller (abstract) */
|
||||
#define TYPE_AW_SDHOST "allwinner-sdhost"
|
||||
|
||||
/** Allwinner sun4i family (A10, A12) */
|
||||
#define TYPE_AW_SDHOST_SUN4I TYPE_AW_SDHOST "-sun4i"
|
||||
|
||||
/** Allwinner sun5i family and newer (A13, H2+, H3, etc) */
|
||||
#define TYPE_AW_SDHOST_SUN5I TYPE_AW_SDHOST "-sun5i"
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Object model macros
|
||||
* @{
|
||||
*/
|
||||
|
||||
#define AW_SDHOST(obj) \
|
||||
OBJECT_CHECK(AwSdHostState, (obj), TYPE_AW_SDHOST)
|
||||
#define AW_SDHOST_CLASS(klass) \
|
||||
OBJECT_CLASS_CHECK(AwSdHostClass, (klass), TYPE_AW_SDHOST)
|
||||
#define AW_SDHOST_GET_CLASS(obj) \
|
||||
OBJECT_GET_CLASS(AwSdHostClass, (obj), TYPE_AW_SDHOST)
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* Allwinner SD Host Controller object instance state.
|
||||
*/
|
||||
typedef struct AwSdHostState {
|
||||
/*< private >*/
|
||||
SysBusDevice busdev;
|
||||
/*< public >*/
|
||||
|
||||
/** Secure Digital (SD) bus, which connects to SD card (if present) */
|
||||
SDBus sdbus;
|
||||
|
||||
/** Maps I/O registers in physical memory */
|
||||
MemoryRegion iomem;
|
||||
|
||||
/** Interrupt output signal to notify CPU */
|
||||
qemu_irq irq;
|
||||
|
||||
/** Number of bytes left in current DMA transfer */
|
||||
uint32_t transfer_cnt;
|
||||
|
||||
/**
|
||||
* @name Hardware Registers
|
||||
* @{
|
||||
*/
|
||||
|
||||
uint32_t global_ctl; /**< Global Control */
|
||||
uint32_t clock_ctl; /**< Clock Control */
|
||||
uint32_t timeout; /**< Timeout */
|
||||
uint32_t bus_width; /**< Bus Width */
|
||||
uint32_t block_size; /**< Block Size */
|
||||
uint32_t byte_count; /**< Byte Count */
|
||||
|
||||
uint32_t command; /**< Command */
|
||||
uint32_t command_arg; /**< Command Argument */
|
||||
uint32_t response[4]; /**< Command Response */
|
||||
|
||||
uint32_t irq_mask; /**< Interrupt Mask */
|
||||
uint32_t irq_status; /**< Raw Interrupt Status */
|
||||
uint32_t status; /**< Status */
|
||||
|
||||
uint32_t fifo_wlevel; /**< FIFO Water Level */
|
||||
uint32_t fifo_func_sel; /**< FIFO Function Select */
|
||||
uint32_t debug_enable; /**< Debug Enable */
|
||||
uint32_t auto12_arg; /**< Auto Command 12 Argument */
|
||||
uint32_t newtiming_set; /**< SD New Timing Set */
|
||||
uint32_t newtiming_debug; /**< SD New Timing Debug */
|
||||
uint32_t hardware_rst; /**< Hardware Reset */
|
||||
uint32_t dmac; /**< Internal DMA Controller Control */
|
||||
uint32_t desc_base; /**< Descriptor List Base Address */
|
||||
uint32_t dmac_status; /**< Internal DMA Controller Status */
|
||||
uint32_t dmac_irq; /**< Internal DMA Controller IRQ Enable */
|
||||
uint32_t card_threshold; /**< Card Threshold Control */
|
||||
uint32_t startbit_detect; /**< eMMC DDR Start Bit Detection Control */
|
||||
uint32_t response_crc; /**< Response CRC */
|
||||
uint32_t data_crc[8]; /**< Data CRC */
|
||||
uint32_t status_crc; /**< Status CRC */
|
||||
|
||||
/** @} */
|
||||
|
||||
} AwSdHostState;
|
||||
|
||||
/**
|
||||
* Allwinner SD Host Controller class-level struct.
|
||||
*
|
||||
* This struct is filled by each sunxi device specific code
|
||||
* such that the generic code can use this struct to support
|
||||
* all devices.
|
||||
*/
|
||||
typedef struct AwSdHostClass {
|
||||
/*< private >*/
|
||||
SysBusDeviceClass parent_class;
|
||||
/*< public >*/
|
||||
|
||||
/** Maximum buffer size in bytes per DMA descriptor */
|
||||
size_t max_desc_size;
|
||||
|
||||
} AwSdHostClass;
|
||||
|
||||
#endif /* HW_SD_ALLWINNER_SDHOST_H */
|
@ -11780,7 +11780,40 @@ bool get_phys_addr(CPUARMState *env, target_ulong address,
|
||||
/* Definitely a real MMU, not an MPU */
|
||||
|
||||
if (regime_translation_disabled(env, mmu_idx)) {
|
||||
/* MMU disabled. */
|
||||
/*
|
||||
* MMU disabled. S1 addresses within aa64 translation regimes are
|
||||
* still checked for bounds -- see AArch64.TranslateAddressS1Off.
|
||||
*/
|
||||
if (mmu_idx != ARMMMUIdx_Stage2) {
|
||||
int r_el = regime_el(env, mmu_idx);
|
||||
if (arm_el_is_aa64(env, r_el)) {
|
||||
int pamax = arm_pamax(env_archcpu(env));
|
||||
uint64_t tcr = env->cp15.tcr_el[r_el].raw_tcr;
|
||||
int addrtop, tbi;
|
||||
|
||||
tbi = aa64_va_parameter_tbi(tcr, mmu_idx);
|
||||
if (access_type == MMU_INST_FETCH) {
|
||||
tbi &= ~aa64_va_parameter_tbid(tcr, mmu_idx);
|
||||
}
|
||||
tbi = (tbi >> extract64(address, 55, 1)) & 1;
|
||||
addrtop = (tbi ? 55 : 63);
|
||||
|
||||
if (extract64(address, pamax, addrtop - pamax + 1) != 0) {
|
||||
fi->type = ARMFault_AddressSize;
|
||||
fi->level = 0;
|
||||
fi->stage2 = false;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* When TBI is disabled, we've just validated that all of the
|
||||
* bits above PAMax are zero, so logically we only need to
|
||||
* clear the top byte for TBI. But it's clearer to follow
|
||||
* the pseudocode set of addrdesc.paddress.
|
||||
*/
|
||||
address = extract64(address, 0, 52);
|
||||
}
|
||||
}
|
||||
*phys_ptr = address;
|
||||
*prot = PAGE_READ | PAGE_WRITE | PAGE_EXEC;
|
||||
*page_size = TARGET_PAGE_SIZE;
|
||||
@ -12468,6 +12501,18 @@ void arm_rebuild_hflags(CPUARMState *env)
|
||||
env->hflags = rebuild_hflags_internal(env);
|
||||
}
|
||||
|
||||
/*
|
||||
* If we have triggered a EL state change we can't rely on the
|
||||
* translator having passed it to us, we need to recompute.
|
||||
*/
|
||||
void HELPER(rebuild_hflags_m32_newel)(CPUARMState *env)
|
||||
{
|
||||
int el = arm_current_el(env);
|
||||
int fp_el = fp_exception_el(env, el);
|
||||
ARMMMUIdx mmu_idx = arm_mmu_idx_el(env, el);
|
||||
env->hflags = rebuild_hflags_m32(env, fp_el, mmu_idx);
|
||||
}
|
||||
|
||||
void HELPER(rebuild_hflags_m32)(CPUARMState *env, int el)
|
||||
{
|
||||
int fp_el = fp_exception_el(env, el);
|
||||
@ -12478,7 +12523,7 @@ void HELPER(rebuild_hflags_m32)(CPUARMState *env, int el)
|
||||
|
||||
/*
|
||||
* If we have triggered a EL state change we can't rely on the
|
||||
* translator having passed it too us, we need to recompute.
|
||||
* translator having passed it to us, we need to recompute.
|
||||
*/
|
||||
void HELPER(rebuild_hflags_a32_newel)(CPUARMState *env)
|
||||
{
|
||||
|
@ -90,6 +90,7 @@ DEF_HELPER_4(msr_banked, void, env, i32, i32, i32)
|
||||
DEF_HELPER_2(get_user_reg, i32, env, i32)
|
||||
DEF_HELPER_3(set_user_reg, void, env, i32, i32)
|
||||
|
||||
DEF_HELPER_FLAGS_1(rebuild_hflags_m32_newel, TCG_CALL_NO_RWG, void, env)
|
||||
DEF_HELPER_FLAGS_2(rebuild_hflags_m32, TCG_CALL_NO_RWG, void, env, int)
|
||||
DEF_HELPER_FLAGS_1(rebuild_hflags_a32_newel, TCG_CALL_NO_RWG, void, env)
|
||||
DEF_HELPER_FLAGS_2(rebuild_hflags_a32, TCG_CALL_NO_RWG, void, env, int)
|
||||
|
@ -874,15 +874,17 @@ int kvm_arch_irqchip_create(KVMState *s)
|
||||
|
||||
int kvm_arm_vgic_probe(void)
|
||||
{
|
||||
int val = 0;
|
||||
|
||||
if (kvm_create_device(kvm_state,
|
||||
KVM_DEV_TYPE_ARM_VGIC_V3, true) == 0) {
|
||||
return 3;
|
||||
} else if (kvm_create_device(kvm_state,
|
||||
KVM_DEV_TYPE_ARM_VGIC_V2, true) == 0) {
|
||||
return 2;
|
||||
} else {
|
||||
return 0;
|
||||
val |= KVM_ARM_VGIC_V3;
|
||||
}
|
||||
if (kvm_create_device(kvm_state,
|
||||
KVM_DEV_TYPE_ARM_VGIC_V2, true) == 0) {
|
||||
val |= KVM_ARM_VGIC_V2;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
int kvm_arm_set_irq(int cpu, int irqtype, int irq, int level)
|
||||
|
@ -409,17 +409,22 @@ int kvm_arch_put_registers(CPUState *cs, int level)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = kvm_put_vcpu_events(cpu);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
write_cpustate_to_list(cpu, true);
|
||||
|
||||
if (!write_list_to_kvmstate(cpu, level)) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Setting VCPU events should be triggered after syncing the registers
|
||||
* to avoid overwriting potential changes made by KVM upon calling
|
||||
* KVM_SET_VCPU_EVENTS ioctl
|
||||
*/
|
||||
ret = kvm_put_vcpu_events(cpu);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
kvm_arm_sync_mpstate_to_kvm(cpu);
|
||||
|
||||
return ret;
|
||||
|
@ -1094,17 +1094,22 @@ int kvm_arch_put_registers(CPUState *cs, int level)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = kvm_put_vcpu_events(cpu);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
write_cpustate_to_list(cpu, true);
|
||||
|
||||
if (!write_list_to_kvmstate(cpu, level)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Setting VCPU events should be triggered after syncing the registers
|
||||
* to avoid overwriting potential changes made by KVM upon calling
|
||||
* KVM_SET_VCPU_EVENTS ioctl
|
||||
*/
|
||||
ret = kvm_put_vcpu_events(cpu);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
kvm_arm_sync_mpstate_to_kvm(cpu);
|
||||
|
||||
return ret;
|
||||
|
@ -15,6 +15,9 @@
|
||||
#include "exec/memory.h"
|
||||
#include "qemu/error-report.h"
|
||||
|
||||
#define KVM_ARM_VGIC_V2 (1 << 0)
|
||||
#define KVM_ARM_VGIC_V3 (1 << 1)
|
||||
|
||||
/**
|
||||
* kvm_arm_vcpu_init:
|
||||
* @cs: CPUState
|
||||
|
@ -228,7 +228,18 @@ static void gen_a64_set_pc(DisasContext *s, TCGv_i64 src)
|
||||
static TCGv_i64 clean_data_tbi(DisasContext *s, TCGv_i64 addr)
|
||||
{
|
||||
TCGv_i64 clean = new_tmp_a64(s);
|
||||
/*
|
||||
* In order to get the correct value in the FAR_ELx register,
|
||||
* we must present the memory subsystem with the "dirty" address
|
||||
* including the TBI. In system mode we can make this work via
|
||||
* the TLB, dropping the TBI during translation. But for user-only
|
||||
* mode we don't have that option, and must remove the top byte now.
|
||||
*/
|
||||
#ifdef CONFIG_USER_ONLY
|
||||
gen_top_byte_ignore(s, clean, addr, s->tbid);
|
||||
#else
|
||||
tcg_gen_mov_i64(clean, addr);
|
||||
#endif
|
||||
return clean;
|
||||
}
|
||||
|
||||
|
@ -7296,7 +7296,7 @@ static int disas_coproc_insn(DisasContext *s, uint32_t insn)
|
||||
|
||||
if (!isread && !(ri->type & ARM_CP_SUPPRESS_TB_END)) {
|
||||
/*
|
||||
* A write to any coprocessor regiser that ends a TB
|
||||
* A write to any coprocessor register that ends a TB
|
||||
* must rebuild the hflags for the next TB.
|
||||
*/
|
||||
TCGv_i32 tcg_el = tcg_const_i32(s->current_el);
|
||||
@ -8551,7 +8551,7 @@ static bool trans_MRS_v7m(DisasContext *s, arg_MRS_v7m *a)
|
||||
|
||||
static bool trans_MSR_v7m(DisasContext *s, arg_MSR_v7m *a)
|
||||
{
|
||||
TCGv_i32 addr, reg, el;
|
||||
TCGv_i32 addr, reg;
|
||||
|
||||
if (!arm_dc_feature(s, ARM_FEATURE_M)) {
|
||||
return false;
|
||||
@ -8561,9 +8561,8 @@ static bool trans_MSR_v7m(DisasContext *s, arg_MSR_v7m *a)
|
||||
gen_helper_v7m_msr(cpu_env, addr, reg);
|
||||
tcg_temp_free_i32(addr);
|
||||
tcg_temp_free_i32(reg);
|
||||
el = tcg_const_i32(s->current_el);
|
||||
gen_helper_rebuild_hflags_m32(cpu_env, el);
|
||||
tcg_temp_free_i32(el);
|
||||
/* If we wrote to CONTROL, the EL might have changed */
|
||||
gen_helper_rebuild_hflags_m32_newel(cpu_env);
|
||||
gen_lookup_tb(s);
|
||||
return true;
|
||||
}
|
||||
@ -10590,7 +10589,7 @@ static bool trans_CPS(DisasContext *s, arg_CPS *a)
|
||||
|
||||
static bool trans_CPS_v7m(DisasContext *s, arg_CPS_v7m *a)
|
||||
{
|
||||
TCGv_i32 tmp, addr;
|
||||
TCGv_i32 tmp, addr, el;
|
||||
|
||||
if (!arm_dc_feature(s, ARM_FEATURE_M)) {
|
||||
return false;
|
||||
@ -10613,6 +10612,9 @@ static bool trans_CPS_v7m(DisasContext *s, arg_CPS_v7m *a)
|
||||
gen_helper_v7m_msr(cpu_env, addr, tmp);
|
||||
tcg_temp_free_i32(addr);
|
||||
}
|
||||
el = tcg_const_i32(s->current_el);
|
||||
gen_helper_rebuild_hflags_m32(cpu_env, el);
|
||||
tcg_temp_free_i32(el);
|
||||
tcg_temp_free_i32(tmp);
|
||||
gen_lookup_tb(s);
|
||||
return true;
|
||||
|
@ -16,10 +16,17 @@ import shutil
|
||||
from avocado import skipUnless
|
||||
from avocado_qemu import Test
|
||||
from avocado_qemu import exec_command_and_wait_for_pattern
|
||||
from avocado_qemu import interrupt_interactive_console_until_pattern
|
||||
from avocado_qemu import wait_for_console_pattern
|
||||
from avocado.utils import process
|
||||
from avocado.utils import archive
|
||||
from avocado.utils.path import find_command, CmdNotFoundError
|
||||
|
||||
P7ZIP_AVAILABLE = True
|
||||
try:
|
||||
find_command('7z')
|
||||
except CmdNotFoundError:
|
||||
P7ZIP_AVAILABLE = False
|
||||
|
||||
class BootLinuxConsole(Test):
|
||||
"""
|
||||
@ -507,6 +514,229 @@ class BootLinuxConsole(Test):
|
||||
exec_command_and_wait_for_pattern(self, 'reboot',
|
||||
'reboot: Restarting system')
|
||||
|
||||
def test_arm_orangepi(self):
|
||||
"""
|
||||
:avocado: tags=arch:arm
|
||||
:avocado: tags=machine:orangepi-pc
|
||||
"""
|
||||
deb_url = ('https://apt.armbian.com/pool/main/l/'
|
||||
'linux-4.20.7-sunxi/linux-image-dev-sunxi_5.75_armhf.deb')
|
||||
deb_hash = '1334c29c44d984ffa05ed10de8c3361f33d78315'
|
||||
deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash)
|
||||
kernel_path = self.extract_from_deb(deb_path,
|
||||
'/boot/vmlinuz-4.20.7-sunxi')
|
||||
dtb_path = '/usr/lib/linux-image-dev-sunxi/sun8i-h3-orangepi-pc.dtb'
|
||||
dtb_path = self.extract_from_deb(deb_path, dtb_path)
|
||||
|
||||
self.vm.set_console()
|
||||
kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE +
|
||||
'console=ttyS0,115200n8 '
|
||||
'earlycon=uart,mmio32,0x1c28000')
|
||||
self.vm.add_args('-kernel', kernel_path,
|
||||
'-dtb', dtb_path,
|
||||
'-append', kernel_command_line)
|
||||
self.vm.launch()
|
||||
console_pattern = 'Kernel command line: %s' % kernel_command_line
|
||||
self.wait_for_console_pattern(console_pattern)
|
||||
|
||||
def test_arm_orangepi_initrd(self):
|
||||
"""
|
||||
:avocado: tags=arch:arm
|
||||
:avocado: tags=machine:orangepi-pc
|
||||
"""
|
||||
deb_url = ('https://apt.armbian.com/pool/main/l/'
|
||||
'linux-4.20.7-sunxi/linux-image-dev-sunxi_5.75_armhf.deb')
|
||||
deb_hash = '1334c29c44d984ffa05ed10de8c3361f33d78315'
|
||||
deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash)
|
||||
kernel_path = self.extract_from_deb(deb_path,
|
||||
'/boot/vmlinuz-4.20.7-sunxi')
|
||||
dtb_path = '/usr/lib/linux-image-dev-sunxi/sun8i-h3-orangepi-pc.dtb'
|
||||
dtb_path = self.extract_from_deb(deb_path, dtb_path)
|
||||
initrd_url = ('https://github.com/groeck/linux-build-test/raw/'
|
||||
'2eb0a73b5d5a28df3170c546ddaaa9757e1e0848/rootfs/'
|
||||
'arm/rootfs-armv7a.cpio.gz')
|
||||
initrd_hash = '604b2e45cdf35045846b8bbfbf2129b1891bdc9c'
|
||||
initrd_path_gz = self.fetch_asset(initrd_url, asset_hash=initrd_hash)
|
||||
initrd_path = os.path.join(self.workdir, 'rootfs.cpio')
|
||||
archive.gzip_uncompress(initrd_path_gz, initrd_path)
|
||||
|
||||
self.vm.set_console()
|
||||
kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE +
|
||||
'console=ttyS0,115200 '
|
||||
'panic=-1 noreboot')
|
||||
self.vm.add_args('-kernel', kernel_path,
|
||||
'-dtb', dtb_path,
|
||||
'-initrd', initrd_path,
|
||||
'-append', kernel_command_line,
|
||||
'-no-reboot')
|
||||
self.vm.launch()
|
||||
self.wait_for_console_pattern('Boot successful.')
|
||||
|
||||
exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo',
|
||||
'Allwinner sun8i Family')
|
||||
exec_command_and_wait_for_pattern(self, 'cat /proc/iomem',
|
||||
'system-control@1c00000')
|
||||
exec_command_and_wait_for_pattern(self, 'reboot',
|
||||
'reboot: Restarting system')
|
||||
|
||||
def test_arm_orangepi_sd(self):
|
||||
"""
|
||||
:avocado: tags=arch:arm
|
||||
:avocado: tags=machine:orangepi-pc
|
||||
"""
|
||||
deb_url = ('https://apt.armbian.com/pool/main/l/'
|
||||
'linux-4.20.7-sunxi/linux-image-dev-sunxi_5.75_armhf.deb')
|
||||
deb_hash = '1334c29c44d984ffa05ed10de8c3361f33d78315'
|
||||
deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash)
|
||||
kernel_path = self.extract_from_deb(deb_path,
|
||||
'/boot/vmlinuz-4.20.7-sunxi')
|
||||
dtb_path = '/usr/lib/linux-image-dev-sunxi/sun8i-h3-orangepi-pc.dtb'
|
||||
dtb_path = self.extract_from_deb(deb_path, dtb_path)
|
||||
rootfs_url = ('http://storage.kernelci.org/images/rootfs/buildroot/'
|
||||
'kci-2019.02/armel/base/rootfs.ext2.xz')
|
||||
rootfs_hash = '692510cb625efda31640d1de0a8d60e26040f061'
|
||||
rootfs_path_xz = self.fetch_asset(rootfs_url, asset_hash=rootfs_hash)
|
||||
rootfs_path = os.path.join(self.workdir, 'rootfs.cpio')
|
||||
archive.lzma_uncompress(rootfs_path_xz, rootfs_path)
|
||||
|
||||
self.vm.set_console()
|
||||
kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE +
|
||||
'console=ttyS0,115200 '
|
||||
'root=/dev/mmcblk0 rootwait rw '
|
||||
'panic=-1 noreboot')
|
||||
self.vm.add_args('-kernel', kernel_path,
|
||||
'-dtb', dtb_path,
|
||||
'-drive', 'file=' + rootfs_path + ',if=sd,format=raw',
|
||||
'-append', kernel_command_line,
|
||||
'-no-reboot')
|
||||
self.vm.launch()
|
||||
shell_ready = "/bin/sh: can't access tty; job control turned off"
|
||||
self.wait_for_console_pattern(shell_ready)
|
||||
|
||||
exec_command_and_wait_for_pattern(self, 'cat /proc/cpuinfo',
|
||||
'Allwinner sun8i Family')
|
||||
exec_command_and_wait_for_pattern(self, 'cat /proc/partitions',
|
||||
'mmcblk0')
|
||||
exec_command_and_wait_for_pattern(self, 'ifconfig eth0 up',
|
||||
'eth0: Link is Up')
|
||||
exec_command_and_wait_for_pattern(self, 'udhcpc eth0',
|
||||
'udhcpc: lease of 10.0.2.15 obtained')
|
||||
exec_command_and_wait_for_pattern(self, 'ping -c 3 10.0.2.2',
|
||||
'3 packets transmitted, 3 packets received, 0% packet loss')
|
||||
exec_command_and_wait_for_pattern(self, 'reboot',
|
||||
'reboot: Restarting system')
|
||||
|
||||
@skipUnless(os.getenv('AVOCADO_ALLOW_LARGE_STORAGE'), 'storage limited')
|
||||
@skipUnless(P7ZIP_AVAILABLE, '7z not installed')
|
||||
def test_arm_orangepi_bionic(self):
|
||||
"""
|
||||
:avocado: tags=arch:arm
|
||||
:avocado: tags=machine:orangepi-pc
|
||||
"""
|
||||
|
||||
# This test download a 196MB compressed image and expand it to 932MB...
|
||||
image_url = ('https://dl.armbian.com/orangepipc/archive/'
|
||||
'Armbian_19.11.3_Orangepipc_bionic_current_5.3.9.7z')
|
||||
image_hash = '196a8ffb72b0123d92cea4a070894813d305c71e'
|
||||
image_path_7z = self.fetch_asset(image_url, asset_hash=image_hash)
|
||||
image_name = 'Armbian_19.11.3_Orangepipc_bionic_current_5.3.9.img'
|
||||
image_path = os.path.join(self.workdir, image_name)
|
||||
process.run("7z e -o%s %s" % (self.workdir, image_path_7z))
|
||||
|
||||
self.vm.set_console()
|
||||
self.vm.add_args('-drive', 'file=' + image_path + ',if=sd,format=raw',
|
||||
'-nic', 'user',
|
||||
'-no-reboot')
|
||||
self.vm.launch()
|
||||
|
||||
kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE +
|
||||
'console=ttyS0,115200 '
|
||||
'loglevel=7 '
|
||||
'nosmp '
|
||||
'systemd.default_timeout_start_sec=9000 '
|
||||
'systemd.mask=armbian-zram-config.service '
|
||||
'systemd.mask=armbian-ramlog.service')
|
||||
|
||||
self.wait_for_console_pattern('U-Boot SPL')
|
||||
self.wait_for_console_pattern('Autoboot in ')
|
||||
exec_command_and_wait_for_pattern(self, ' ', '=>')
|
||||
exec_command_and_wait_for_pattern(self, "setenv extraargs '" +
|
||||
kernel_command_line + "'", '=>')
|
||||
exec_command_and_wait_for_pattern(self, 'boot', 'Starting kernel ...');
|
||||
|
||||
self.wait_for_console_pattern('systemd[1]: Set hostname ' +
|
||||
'to <orangepipc>')
|
||||
self.wait_for_console_pattern('Starting Load Kernel Modules...')
|
||||
|
||||
@skipUnless(os.getenv('AVOCADO_ALLOW_LARGE_STORAGE'), 'storage limited')
|
||||
def test_arm_orangepi_uboot_netbsd9(self):
|
||||
"""
|
||||
:avocado: tags=arch:arm
|
||||
:avocado: tags=machine:orangepi-pc
|
||||
"""
|
||||
# This test download a 304MB compressed image and expand it to 1.3GB...
|
||||
deb_url = ('http://snapshot.debian.org/archive/debian/'
|
||||
'20200108T145233Z/pool/main/u/u-boot/'
|
||||
'u-boot-sunxi_2020.01%2Bdfsg-1_armhf.deb')
|
||||
deb_hash = 'f67f404a80753ca3d1258f13e38f2b060e13db99'
|
||||
deb_path = self.fetch_asset(deb_url, asset_hash=deb_hash)
|
||||
# We use the common OrangePi PC 'plus' build of U-Boot for our secondary
|
||||
# program loader (SPL). We will then set the path to the more specific
|
||||
# OrangePi "PC" device tree blob with 'setenv fdtfile' in U-Boot prompt,
|
||||
# before to boot NetBSD.
|
||||
uboot_path = '/usr/lib/u-boot/orangepi_plus/u-boot-sunxi-with-spl.bin'
|
||||
uboot_path = self.extract_from_deb(deb_path, uboot_path)
|
||||
image_url = ('https://cdn.netbsd.org/pub/NetBSD/NetBSD-9.0/'
|
||||
'evbarm-earmv7hf/binary/gzimg/armv7.img.gz')
|
||||
image_hash = '2babb29d36d8360adcb39c09e31060945259917a'
|
||||
image_path_gz = self.fetch_asset(image_url, asset_hash=image_hash)
|
||||
image_path = os.path.join(self.workdir, 'armv7.img')
|
||||
image_drive_args = 'if=sd,format=raw,snapshot=on,file=' + image_path
|
||||
archive.gzip_uncompress(image_path_gz, image_path)
|
||||
|
||||
# dd if=u-boot-sunxi-with-spl.bin of=armv7.img bs=1K seek=8 conv=notrunc
|
||||
with open(uboot_path, 'rb') as f_in:
|
||||
with open(image_path, 'r+b') as f_out:
|
||||
f_out.seek(8 * 1024)
|
||||
shutil.copyfileobj(f_in, f_out)
|
||||
|
||||
# Extend image, to avoid that NetBSD thinks the partition
|
||||
# inside the image is larger than device size itself
|
||||
f_out.seek(0, 2)
|
||||
f_out.seek(64 * 1024 * 1024, 1)
|
||||
f_out.write(bytearray([0x00]))
|
||||
|
||||
self.vm.set_console()
|
||||
self.vm.add_args('-nic', 'user',
|
||||
'-drive', image_drive_args,
|
||||
'-global', 'allwinner-rtc.base-year=2000',
|
||||
'-no-reboot')
|
||||
self.vm.launch()
|
||||
wait_for_console_pattern(self, 'U-Boot 2020.01+dfsg-1')
|
||||
interrupt_interactive_console_until_pattern(self,
|
||||
'Hit any key to stop autoboot:',
|
||||
'switch to partitions #0, OK')
|
||||
|
||||
exec_command_and_wait_for_pattern(self, '', '=>')
|
||||
cmd = 'setenv bootargs root=ld0a'
|
||||
exec_command_and_wait_for_pattern(self, cmd, '=>')
|
||||
cmd = 'setenv kernel netbsd-GENERIC.ub'
|
||||
exec_command_and_wait_for_pattern(self, cmd, '=>')
|
||||
cmd = 'setenv fdtfile dtb/sun8i-h3-orangepi-pc.dtb'
|
||||
exec_command_and_wait_for_pattern(self, cmd, '=>')
|
||||
cmd = ("setenv bootcmd 'fatload mmc 0:1 ${kernel_addr_r} ${kernel}; "
|
||||
"fatload mmc 0:1 ${fdt_addr_r} ${fdtfile}; "
|
||||
"fdt addr ${fdt_addr_r}; "
|
||||
"bootm ${kernel_addr_r} - ${fdt_addr_r}'")
|
||||
exec_command_and_wait_for_pattern(self, cmd, '=>')
|
||||
|
||||
exec_command_and_wait_for_pattern(self, 'boot',
|
||||
'Booting kernel from Legacy Image')
|
||||
wait_for_console_pattern(self, 'Starting kernel ...')
|
||||
wait_for_console_pattern(self, 'NetBSD 9.0 (GENERIC)')
|
||||
# Wait for user-space
|
||||
wait_for_console_pattern(self, 'Starting root file system check')
|
||||
|
||||
def test_s390x_s390_ccw_virtio(self):
|
||||
"""
|
||||
:avocado: tags=arch:s390x
|
||||
|
Loading…
Reference in New Issue
Block a user