mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
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:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
+113
-32
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user