On Wed, May 10, 2023 at 12:30 PM <[email protected]> wrote:
> From: qianfan Zhao <[email protected]>
>
> The CCU provides the registers to program the PLLs and the controls
> most of the clock generation, division, distribution, synchronization
> and gating.
>
> This commit adds support for the Clock Control Unit which emulates
> a simple read/write register interface.
>
> Signed-off-by: qianfan Zhao <[email protected]>
>
Reviewed-by: Niek Linnenbank <[email protected]>
> ---
> hw/arm/allwinner-r40.c | 8 +-
> hw/misc/allwinner-r40-ccu.c | 209 ++++++++++++++++++++++++++++
> hw/misc/meson.build | 1 +
> include/hw/arm/allwinner-r40.h | 2 +
> include/hw/misc/allwinner-r40-ccu.h | 65 +++++++++
> 5 files changed, 284 insertions(+), 1 deletion(-)
> create mode 100644 hw/misc/allwinner-r40-ccu.c
> create mode 100644 include/hw/misc/allwinner-r40-ccu.h
>
> diff --git a/hw/arm/allwinner-r40.c b/hw/arm/allwinner-r40.c
> index b743d64253..128c0ca470 100644
> --- a/hw/arm/allwinner-r40.c
> +++ b/hw/arm/allwinner-r40.c
> @@ -42,6 +42,7 @@ const hwaddr allwinner_r40_memmap[] = {
> [AW_R40_DEV_MMC1] = 0x01c10000,
> [AW_R40_DEV_MMC2] = 0x01c11000,
> [AW_R40_DEV_MMC3] = 0x01c12000,
> + [AW_R40_DEV_CCU] = 0x01c20000,
> [AW_R40_DEV_PIT] = 0x01c20c00,
> [AW_R40_DEV_UART0] = 0x01c28000,
> [AW_R40_DEV_GIC_DIST] = 0x01c81000,
> @@ -80,7 +81,6 @@ static struct AwR40Unimplemented r40_unimplemented[] = {
> { "usb2-host", 0x01c1c000, 4 * KiB },
> { "cs1", 0x01c1d000, 4 * KiB },
> { "spi3", 0x01c1f000, 4 * KiB },
> - { "ccu", 0x01c20000, 1 * KiB },
> { "rtc", 0x01c20400, 1 * KiB },
> { "pio", 0x01c20800, 1 * KiB },
> { "owa", 0x01c21000, 1 * KiB },
> @@ -253,6 +253,8 @@ static void allwinner_r40_init(Object *obj)
> object_property_add_alias(obj, "clk1-freq", OBJECT(&s->timer),
> "clk1-freq");
>
> + object_initialize_child(obj, "ccu", &s->ccu, TYPE_AW_R40_CCU);
> +
> for (int i = 0; i < AW_R40_NUM_MMCS; i++) {
> object_initialize_child(obj, mmc_names[i], &s->mmc[i],
> TYPE_AW_SDHOST_SUN5I);
> @@ -367,6 +369,10 @@ static void allwinner_r40_realize(DeviceState *dev,
> Error **errp)
> memory_region_add_subregion(get_system_memory(),
> s->memmap[AW_R40_DEV_SRAM_A4],
> &s->sram_a4);
>
> + /* Clock Control Unit */
> + sysbus_realize(SYS_BUS_DEVICE(&s->ccu), &error_fatal);
> + sysbus_mmio_map(SYS_BUS_DEVICE(&s->ccu), 0,
> s->memmap[AW_R40_DEV_CCU]);
> +
> /* SD/MMC */
> for (int i = 0; i < AW_R40_NUM_MMCS; i++) {
> qemu_irq irq = qdev_get_gpio_in(DEVICE(&s->gic),
> diff --git a/hw/misc/allwinner-r40-ccu.c b/hw/misc/allwinner-r40-ccu.c
> new file mode 100644
> index 0000000000..d82fee12db
> --- /dev/null
> +++ b/hw/misc/allwinner-r40-ccu.c
> @@ -0,0 +1,209 @@
> +/*
> + * Allwinner R40 Clock Control Unit emulation
> + *
> + * Copyright (C) 2023 qianfan Zhao <[email protected]>
> + *
> + * This program is free software: you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation, either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program. If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/units.h"
> +#include "hw/sysbus.h"
> +#include "migration/vmstate.h"
> +#include "qemu/log.h"
> +#include "qemu/module.h"
> +#include "hw/misc/allwinner-r40-ccu.h"
> +
> +/* CCU register offsets */
> +enum {
> + REG_PLL_CPUX_CTRL = 0x0000,
> + REG_PLL_AUDIO_CTRL = 0x0008,
> + REG_PLL_VIDEO0_CTRL = 0x0010,
> + REG_PLL_VE_CTRL = 0x0018,
> + REG_PLL_DDR0_CTRL = 0x0020,
> + REG_PLL_PERIPH0_CTRL = 0x0028,
> + REG_PLL_PERIPH1_CTRL = 0x002c,
> + REG_PLL_VIDEO1_CTRL = 0x0030,
> + REG_PLL_SATA_CTRL = 0x0034,
> + REG_PLL_GPU_CTRL = 0x0038,
> + REG_PLL_MIPI_CTRL = 0x0040,
> + REG_PLL_DE_CTRL = 0x0048,
> + REG_PLL_DDR1_CTRL = 0x004c,
> + REG_AHB1_APB1_CFG = 0x0054,
> + REG_APB2_CFG = 0x0058,
> + REG_MMC0_CLK = 0x0088,
> + REG_MMC1_CLK = 0x008c,
> + REG_MMC2_CLK = 0x0090,
> + REG_MMC3_CLK = 0x0094,
> + REG_USBPHY_CFG = 0x00cc,
> + REG_PLL_DDR_AUX = 0x00f0,
> + REG_DRAM_CFG = 0x00f4,
> + REG_PLL_DDR1_CFG = 0x00f8,
> + REG_DRAM_CLK_GATING = 0x0100,
> + REG_GMAC_CLK = 0x0164,
> + REG_SYS_32K_CLK = 0x0310,
> + REG_PLL_LOCK_CTRL = 0x0320,
> +};
> +
> +#define REG_INDEX(offset) (offset / sizeof(uint32_t))
> +
> +/* CCU register flags */
> +enum {
> + REG_PLL_ENABLE = (1 << 31),
> + REG_PLL_LOCK = (1 << 28),
> +};
> +
> +static uint64_t allwinner_r40_ccu_read(void *opaque, hwaddr offset,
> + unsigned size)
> +{
> + const AwR40ClockCtlState *s = AW_R40_CCU(opaque);
> + const uint32_t idx = REG_INDEX(offset);
> +
> + switch (offset) {
> + case 0x324 ... AW_R40_CCU_IOSIZE:
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset
> 0x%04x\n",
> + __func__, (uint32_t)offset);
> + return 0;
> + }
> +
> + return s->regs[idx];
> +}
> +
> +static void allwinner_r40_ccu_write(void *opaque, hwaddr offset,
> + uint64_t val, unsigned size)
> +{
> + AwR40ClockCtlState *s = AW_R40_CCU(opaque);
> +
> + switch (offset) {
> + case REG_DRAM_CFG: /* DRAM Configuration(for DDR0) */
> + /* bit16: SDRCLK_UPD (SDRCLK configuration 0 update) */
> + val &= ~(1 << 16);
> + break;
> + case REG_PLL_DDR1_CTRL: /* DDR1 Control register */
> + /* bit30: SDRPLL_UPD */
> + val &= ~(1 << 30);
> + if (val & REG_PLL_ENABLE) {
> + val |= REG_PLL_LOCK;
> + }
> + break;
> + case REG_PLL_CPUX_CTRL:
> + case REG_PLL_AUDIO_CTRL:
> + case REG_PLL_VE_CTRL:
> + case REG_PLL_VIDEO0_CTRL:
> + case REG_PLL_DDR0_CTRL:
> + case REG_PLL_PERIPH0_CTRL:
> + case REG_PLL_PERIPH1_CTRL:
> + case REG_PLL_VIDEO1_CTRL:
> + case REG_PLL_SATA_CTRL:
> + case REG_PLL_GPU_CTRL:
> + case REG_PLL_MIPI_CTRL:
> + case REG_PLL_DE_CTRL:
> + if (val & REG_PLL_ENABLE) {
> + val |= REG_PLL_LOCK;
> + }
> + break;
> + case 0x324 ... AW_R40_CCU_IOSIZE:
> + qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset
> 0x%04x\n",
> + __func__, (uint32_t)offset);
> + break;
> + default:
> + qemu_log_mask(LOG_UNIMP, "%s: unimplemented write offset
> 0x%04x\n",
> + __func__, (uint32_t)offset);
> + break;
> + }
> +
> + s->regs[REG_INDEX(offset)] = (uint32_t) val;
> +}
> +
> +static const MemoryRegionOps allwinner_r40_ccu_ops = {
> + .read = allwinner_r40_ccu_read,
> + .write = allwinner_r40_ccu_write,
> + .endianness = DEVICE_NATIVE_ENDIAN,
> + .valid = {
> + .min_access_size = 4,
> + .max_access_size = 4,
> + },
> + .impl.min_access_size = 4,
> +};
> +
> +static void allwinner_r40_ccu_reset(DeviceState *dev)
> +{
> + AwR40ClockCtlState *s = AW_R40_CCU(dev);
> +
> + memset(s->regs, 0, sizeof(s->regs));
> +
> + /* Set default values for registers */
> + s->regs[REG_INDEX(REG_PLL_CPUX_CTRL)] = 0x00001000;
> + s->regs[REG_INDEX(REG_PLL_AUDIO_CTRL)] = 0x00035514;
> + s->regs[REG_INDEX(REG_PLL_VIDEO0_CTRL)] = 0x03006207;
> + s->regs[REG_INDEX(REG_PLL_VE_CTRL)] = 0x03006207;
> + s->regs[REG_INDEX(REG_PLL_DDR0_CTRL)] = 0x00001000,
> + s->regs[REG_INDEX(REG_PLL_PERIPH0_CTRL)] = 0x00041811;
> + s->regs[REG_INDEX(REG_PLL_PERIPH1_CTRL)] = 0x00041811;
> + s->regs[REG_INDEX(REG_PLL_VIDEO1_CTRL)] = 0x03006207;
> + s->regs[REG_INDEX(REG_PLL_SATA_CTRL)] = 0x00001811;
> + s->regs[REG_INDEX(REG_PLL_GPU_CTRL)] = 0x03006207;
> + s->regs[REG_INDEX(REG_PLL_MIPI_CTRL)] = 0x00000515;
> + s->regs[REG_INDEX(REG_PLL_DE_CTRL)] = 0x03006207;
> + s->regs[REG_INDEX(REG_PLL_DDR1_CTRL)] = 0x00001800;
> + s->regs[REG_INDEX(REG_AHB1_APB1_CFG)] = 0x00001010;
> + s->regs[REG_INDEX(REG_APB2_CFG)] = 0x01000000;
> + s->regs[REG_INDEX(REG_PLL_DDR_AUX)] = 0x00000001;
> + s->regs[REG_INDEX(REG_PLL_DDR1_CFG)] = 0x0ccca000;
> + s->regs[REG_INDEX(REG_SYS_32K_CLK)] = 0x0000000f;
> +}
> +
> +static void allwinner_r40_ccu_init(Object *obj)
> +{
> + SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
> + AwR40ClockCtlState *s = AW_R40_CCU(obj);
> +
> + /* Memory mapping */
> + memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_r40_ccu_ops, s,
> + TYPE_AW_R40_CCU, AW_R40_CCU_IOSIZE);
> + sysbus_init_mmio(sbd, &s->iomem);
> +}
> +
> +static const VMStateDescription allwinner_r40_ccu_vmstate = {
> + .name = "allwinner-r40-ccu",
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .fields = (VMStateField[]) {
> + VMSTATE_UINT32_ARRAY(regs, AwR40ClockCtlState,
> AW_R40_CCU_REGS_NUM),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static void allwinner_r40_ccu_class_init(ObjectClass *klass, void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> +
> + dc->reset = allwinner_r40_ccu_reset;
> + dc->vmsd = &allwinner_r40_ccu_vmstate;
> +}
> +
> +static const TypeInfo allwinner_r40_ccu_info = {
> + .name = TYPE_AW_R40_CCU,
> + .parent = TYPE_SYS_BUS_DEVICE,
> + .instance_init = allwinner_r40_ccu_init,
> + .instance_size = sizeof(AwR40ClockCtlState),
> + .class_init = allwinner_r40_ccu_class_init,
> +};
> +
> +static void allwinner_r40_ccu_register(void)
> +{
> + type_register_static(&allwinner_r40_ccu_info);
> +}
> +
> +type_init(allwinner_r40_ccu_register)
> diff --git a/hw/misc/meson.build b/hw/misc/meson.build
> index a40245ad44..96e35f1cdb 100644
> --- a/hw/misc/meson.build
> +++ b/hw/misc/meson.build
> @@ -44,6 +44,7 @@ specific_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true:
> files('allwinner-cpucfg.c'
> softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true:
> files('allwinner-h3-dramc.c'))
> softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true:
> files('allwinner-h3-sysctrl.c'))
> softmmu_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true:
> files('allwinner-sid.c'))
> +softmmu_ss.add(when: 'CONFIG_ALLWINNER_R40', if_true:
> files('allwinner-r40-ccu.c'))
> softmmu_ss.add(when: 'CONFIG_AXP209_PMU', if_true: files('axp209.c'))
> softmmu_ss.add(when: 'CONFIG_REALVIEW', if_true: files('arm_sysctl.c'))
> softmmu_ss.add(when: 'CONFIG_NSERIES', if_true: files('cbus.c'))
> diff --git a/include/hw/arm/allwinner-r40.h
> b/include/hw/arm/allwinner-r40.h
> index 348bf25d6b..3be9dc962b 100644
> --- a/include/hw/arm/allwinner-r40.h
> +++ b/include/hw/arm/allwinner-r40.h
> @@ -25,6 +25,7 @@
> #include "hw/timer/allwinner-a10-pit.h"
> #include "hw/intc/arm_gic.h"
> #include "hw/sd/allwinner-sdhost.h"
> +#include "hw/misc/allwinner-r40-ccu.h"
> #include "target/arm/cpu.h"
> #include "sysemu/block-backend.h"
>
> @@ -79,6 +80,7 @@ struct AwR40State {
> const hwaddr *memmap;
> AwA10PITState timer;
> AwSdHostState mmc[AW_R40_NUM_MMCS];
> + AwR40ClockCtlState ccu;
> GICState gic;
> MemoryRegion sram_a1;
> MemoryRegion sram_a2;
> diff --git a/include/hw/misc/allwinner-r40-ccu.h
> b/include/hw/misc/allwinner-r40-ccu.h
> new file mode 100644
> index 0000000000..ceb74eff92
> --- /dev/null
> +++ b/include/hw/misc/allwinner-r40-ccu.h
> @@ -0,0 +1,65 @@
> +/*
> + * Allwinner R40 Clock Control Unit emulation
> + *
> + * Copyright (C) 2023 qianfan Zhao <[email protected]>
> + *
> + * 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_R40_CCU_H
> +#define HW_MISC_ALLWINNER_R40_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_R40_CCU_IOSIZE (0x400)
> +
> +/** Total number of known registers */
> +#define AW_R40_CCU_REGS_NUM (AW_R40_CCU_IOSIZE / sizeof(uint32_t))
> +
> +/** @} */
> +
> +/**
> + * @name Object model
> + * @{
> + */
> +
> +#define TYPE_AW_R40_CCU "allwinner-r40-ccu"
> +OBJECT_DECLARE_SIMPLE_TYPE(AwR40ClockCtlState, AW_R40_CCU)
> +
> +/** @} */
> +
> +/**
> + * Allwinner R40 CCU object instance state.
> + */
> +struct AwR40ClockCtlState {
> + /*< private >*/
> + SysBusDevice parent_obj;
> + /*< public >*/
> +
> + /** Maps I/O registers in physical memory */
> + MemoryRegion iomem;
> +
> + /** Array of hardware registers */
> + uint32_t regs[AW_R40_CCU_REGS_NUM];
> +
> +};
> +
> +#endif /* HW_MISC_ALLWINNER_R40_CCU_H */
> --
> 2.25.1
>
>
--
Niek Linnenbank