From e9e0ef15d2357eca058af71c35eba51093b0c0fb Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Fri, 9 Feb 2018 10:40:29 +0000 Subject: [PATCH] i.MX: Add code to emulate i.MX7 CCM, PMU and ANALOG IP blocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add minimal code needed to allow upstream Linux guest to boot. Cc: Peter Maydell Cc: Jason Wang Cc: Philippe Mathieu-Daudé Cc: Marcel Apfelbaum Cc: Michael S. Tsirkin Cc: qemu-devel@nongnu.org Cc: qemu-arm@nongnu.org Cc: yurovsky@gmail.com Reviewed-by: Peter Maydell Signed-off-by: Andrey Smirnov Signed-off-by: Peter Maydell --- hw/misc/Makefile.objs | 1 + hw/misc/imx7_ccm.c | 277 +++++++++++++++++++++++++++++++++++++ include/hw/misc/imx7_ccm.h | 139 +++++++++++++++++++ 3 files changed, 417 insertions(+) create mode 100644 hw/misc/imx7_ccm.c create mode 100644 include/hw/misc/imx7_ccm.h diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs index d517f83e81..a28e5e49b0 100644 --- a/hw/misc/Makefile.objs +++ b/hw/misc/Makefile.objs @@ -33,6 +33,7 @@ obj-$(CONFIG_IMX) += imx31_ccm.o obj-$(CONFIG_IMX) += imx25_ccm.o obj-$(CONFIG_IMX) += imx6_ccm.o obj-$(CONFIG_IMX) += imx6_src.o +obj-$(CONFIG_IMX) += imx7_ccm.o obj-$(CONFIG_MILKYMIST) += milkymist-hpdmc.o obj-$(CONFIG_MILKYMIST) += milkymist-pfpu.o obj-$(CONFIG_MAINSTONE) += mst_fpga.o diff --git a/hw/misc/imx7_ccm.c b/hw/misc/imx7_ccm.c new file mode 100644 index 0000000000..d90c48bfec --- /dev/null +++ b/hw/misc/imx7_ccm.c @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2018, Impinj, Inc. + * + * i.MX7 CCM, PMU and ANALOG IP blocks emulation code + * + * Author: Andrey Smirnov + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" + +#include "hw/misc/imx7_ccm.h" + +static void imx7_analog_reset(DeviceState *dev) +{ + IMX7AnalogState *s = IMX7_ANALOG(dev); + + memset(s->pmu, 0, sizeof(s->pmu)); + memset(s->analog, 0, sizeof(s->analog)); + + s->analog[ANALOG_PLL_ARM] = 0x00002042; + s->analog[ANALOG_PLL_DDR] = 0x0060302c; + s->analog[ANALOG_PLL_DDR_SS] = 0x00000000; + s->analog[ANALOG_PLL_DDR_NUM] = 0x06aaac4d; + s->analog[ANALOG_PLL_DDR_DENOM] = 0x100003ec; + s->analog[ANALOG_PLL_480] = 0x00002000; + s->analog[ANALOG_PLL_480A] = 0x52605a56; + s->analog[ANALOG_PLL_480B] = 0x52525216; + s->analog[ANALOG_PLL_ENET] = 0x00001fc0; + s->analog[ANALOG_PLL_AUDIO] = 0x0001301b; + s->analog[ANALOG_PLL_AUDIO_SS] = 0x00000000; + s->analog[ANALOG_PLL_AUDIO_NUM] = 0x05f5e100; + s->analog[ANALOG_PLL_AUDIO_DENOM] = 0x2964619c; + s->analog[ANALOG_PLL_VIDEO] = 0x0008201b; + s->analog[ANALOG_PLL_VIDEO_SS] = 0x00000000; + s->analog[ANALOG_PLL_VIDEO_NUM] = 0x0000f699; + s->analog[ANALOG_PLL_VIDEO_DENOM] = 0x000f4240; + s->analog[ANALOG_PLL_MISC0] = 0x00000000; + + /* all PLLs need to be locked */ + s->analog[ANALOG_PLL_ARM] |= ANALOG_PLL_LOCK; + s->analog[ANALOG_PLL_DDR] |= ANALOG_PLL_LOCK; + s->analog[ANALOG_PLL_480] |= ANALOG_PLL_LOCK; + s->analog[ANALOG_PLL_480A] |= ANALOG_PLL_LOCK; + s->analog[ANALOG_PLL_480B] |= ANALOG_PLL_LOCK; + s->analog[ANALOG_PLL_ENET] |= ANALOG_PLL_LOCK; + s->analog[ANALOG_PLL_AUDIO] |= ANALOG_PLL_LOCK; + s->analog[ANALOG_PLL_VIDEO] |= ANALOG_PLL_LOCK; + s->analog[ANALOG_PLL_MISC0] |= ANALOG_PLL_LOCK; + + /* + * Since I couldn't find any info about this in the reference + * manual the value of this register is based strictly on matching + * what Linux kernel expects it to be. + */ + s->analog[ANALOG_DIGPROG] = 0x720000; + /* + * Set revision to be 1.0 (Arbitrary choice, no particular + * reason). + */ + s->analog[ANALOG_DIGPROG] |= 0x000010; +} + +static void imx7_ccm_reset(DeviceState *dev) +{ + IMX7CCMState *s = IMX7_CCM(dev); + + memset(s->ccm, 0, sizeof(s->ccm)); +} + +#define CCM_INDEX(offset) (((offset) & ~(hwaddr)0xF) / sizeof(uint32_t)) +#define CCM_BITOP(offset) ((offset) & (hwaddr)0xF) + +enum { + CCM_BITOP_NONE = 0x00, + CCM_BITOP_SET = 0x04, + CCM_BITOP_CLR = 0x08, + CCM_BITOP_TOG = 0x0C, +}; + +static uint64_t imx7_set_clr_tog_read(void *opaque, hwaddr offset, + unsigned size) +{ + const uint32_t *mmio = opaque; + + return mmio[CCM_INDEX(offset)]; +} + +static void imx7_set_clr_tog_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + const uint8_t bitop = CCM_BITOP(offset); + const uint32_t index = CCM_INDEX(offset); + uint32_t *mmio = opaque; + + switch (bitop) { + case CCM_BITOP_NONE: + mmio[index] = value; + break; + case CCM_BITOP_SET: + mmio[index] |= value; + break; + case CCM_BITOP_CLR: + mmio[index] &= ~value; + break; + case CCM_BITOP_TOG: + mmio[index] ^= value; + break; + }; +} + +static const struct MemoryRegionOps imx7_set_clr_tog_ops = { + .read = imx7_set_clr_tog_read, + .write = imx7_set_clr_tog_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static const struct MemoryRegionOps imx7_digprog_ops = { + .read = imx7_set_clr_tog_read, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void imx7_ccm_init(Object *obj) +{ + SysBusDevice *sd = SYS_BUS_DEVICE(obj); + IMX7CCMState *s = IMX7_CCM(obj); + + memory_region_init_io(&s->iomem, + obj, + &imx7_set_clr_tog_ops, + s->ccm, + TYPE_IMX7_CCM ".ccm", + sizeof(s->ccm)); + + sysbus_init_mmio(sd, &s->iomem); +} + +static void imx7_analog_init(Object *obj) +{ + SysBusDevice *sd = SYS_BUS_DEVICE(obj); + IMX7AnalogState *s = IMX7_ANALOG(obj); + + memory_region_init(&s->mmio.container, obj, TYPE_IMX7_ANALOG, + 0x10000); + + memory_region_init_io(&s->mmio.analog, + obj, + &imx7_set_clr_tog_ops, + s->analog, + TYPE_IMX7_ANALOG, + sizeof(s->analog)); + + memory_region_add_subregion(&s->mmio.container, + 0x60, &s->mmio.analog); + + memory_region_init_io(&s->mmio.pmu, + obj, + &imx7_set_clr_tog_ops, + s->pmu, + TYPE_IMX7_ANALOG ".pmu", + sizeof(s->pmu)); + + memory_region_add_subregion(&s->mmio.container, + 0x200, &s->mmio.pmu); + + memory_region_init_io(&s->mmio.digprog, + obj, + &imx7_digprog_ops, + &s->analog[ANALOG_DIGPROG], + TYPE_IMX7_ANALOG ".digprog", + sizeof(uint32_t)); + + memory_region_add_subregion_overlap(&s->mmio.container, + 0x800, &s->mmio.digprog, 10); + + + sysbus_init_mmio(sd, &s->mmio.container); +} + +static const VMStateDescription vmstate_imx7_ccm = { + .name = TYPE_IMX7_CCM, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(ccm, IMX7CCMState, CCM_MAX), + VMSTATE_END_OF_LIST() + }, +}; + +static uint32_t imx7_ccm_get_clock_frequency(IMXCCMState *dev, IMXClk clock) +{ + /* + * This function is "consumed" by GPT emulation code, however on + * i.MX7 each GPT block can have their own clock root. This means + * that this functions needs somehow to know requester's identity + * and the way to pass it: be it via additional IMXClk constants + * or by adding another argument to this method needs to be + * figured out + */ + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Not implemented\n", + TYPE_IMX7_CCM, __func__); + return 0; +} + +static void imx7_ccm_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + IMXCCMClass *ccm = IMX_CCM_CLASS(klass); + + dc->reset = imx7_ccm_reset; + dc->vmsd = &vmstate_imx7_ccm; + dc->desc = "i.MX7 Clock Control Module"; + + ccm->get_clock_frequency = imx7_ccm_get_clock_frequency; +} + +static const TypeInfo imx7_ccm_info = { + .name = TYPE_IMX7_CCM, + .parent = TYPE_IMX_CCM, + .instance_size = sizeof(IMX7CCMState), + .instance_init = imx7_ccm_init, + .class_init = imx7_ccm_class_init, +}; + +static const VMStateDescription vmstate_imx7_analog = { + .name = TYPE_IMX7_ANALOG, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(analog, IMX7AnalogState, ANALOG_MAX), + VMSTATE_UINT32_ARRAY(pmu, IMX7AnalogState, PMU_MAX), + VMSTATE_END_OF_LIST() + }, +}; + +static void imx7_analog_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = imx7_analog_reset; + dc->vmsd = &vmstate_imx7_analog; + dc->desc = "i.MX7 Analog Module"; +} + +static const TypeInfo imx7_analog_info = { + .name = TYPE_IMX7_ANALOG, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IMX7AnalogState), + .instance_init = imx7_analog_init, + .class_init = imx7_analog_class_init, +}; + +static void imx7_ccm_register_type(void) +{ + type_register_static(&imx7_ccm_info); + type_register_static(&imx7_analog_info); +} +type_init(imx7_ccm_register_type) diff --git a/include/hw/misc/imx7_ccm.h b/include/hw/misc/imx7_ccm.h new file mode 100644 index 0000000000..9538f37d98 --- /dev/null +++ b/include/hw/misc/imx7_ccm.h @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2017, Impinj, Inc. + * + * i.MX7 CCM, PMU and ANALOG IP blocks emulation code + * + * Author: Andrey Smirnov + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#ifndef IMX7_CCM_H +#define IMX7_CCM_H + +#include "hw/misc/imx_ccm.h" +#include "qemu/bitops.h" + +enum IMX7AnalogRegisters { + ANALOG_PLL_ARM, + ANALOG_PLL_ARM_SET, + ANALOG_PLL_ARM_CLR, + ANALOG_PLL_ARM_TOG, + ANALOG_PLL_DDR, + ANALOG_PLL_DDR_SET, + ANALOG_PLL_DDR_CLR, + ANALOG_PLL_DDR_TOG, + ANALOG_PLL_DDR_SS, + ANALOG_PLL_DDR_SS_SET, + ANALOG_PLL_DDR_SS_CLR, + ANALOG_PLL_DDR_SS_TOG, + ANALOG_PLL_DDR_NUM, + ANALOG_PLL_DDR_NUM_SET, + ANALOG_PLL_DDR_NUM_CLR, + ANALOG_PLL_DDR_NUM_TOG, + ANALOG_PLL_DDR_DENOM, + ANALOG_PLL_DDR_DENOM_SET, + ANALOG_PLL_DDR_DENOM_CLR, + ANALOG_PLL_DDR_DENOM_TOG, + ANALOG_PLL_480, + ANALOG_PLL_480_SET, + ANALOG_PLL_480_CLR, + ANALOG_PLL_480_TOG, + ANALOG_PLL_480A, + ANALOG_PLL_480A_SET, + ANALOG_PLL_480A_CLR, + ANALOG_PLL_480A_TOG, + ANALOG_PLL_480B, + ANALOG_PLL_480B_SET, + ANALOG_PLL_480B_CLR, + ANALOG_PLL_480B_TOG, + ANALOG_PLL_ENET, + ANALOG_PLL_ENET_SET, + ANALOG_PLL_ENET_CLR, + ANALOG_PLL_ENET_TOG, + ANALOG_PLL_AUDIO, + ANALOG_PLL_AUDIO_SET, + ANALOG_PLL_AUDIO_CLR, + ANALOG_PLL_AUDIO_TOG, + ANALOG_PLL_AUDIO_SS, + ANALOG_PLL_AUDIO_SS_SET, + ANALOG_PLL_AUDIO_SS_CLR, + ANALOG_PLL_AUDIO_SS_TOG, + ANALOG_PLL_AUDIO_NUM, + ANALOG_PLL_AUDIO_NUM_SET, + ANALOG_PLL_AUDIO_NUM_CLR, + ANALOG_PLL_AUDIO_NUM_TOG, + ANALOG_PLL_AUDIO_DENOM, + ANALOG_PLL_AUDIO_DENOM_SET, + ANALOG_PLL_AUDIO_DENOM_CLR, + ANALOG_PLL_AUDIO_DENOM_TOG, + ANALOG_PLL_VIDEO, + ANALOG_PLL_VIDEO_SET, + ANALOG_PLL_VIDEO_CLR, + ANALOG_PLL_VIDEO_TOG, + ANALOG_PLL_VIDEO_SS, + ANALOG_PLL_VIDEO_SS_SET, + ANALOG_PLL_VIDEO_SS_CLR, + ANALOG_PLL_VIDEO_SS_TOG, + ANALOG_PLL_VIDEO_NUM, + ANALOG_PLL_VIDEO_NUM_SET, + ANALOG_PLL_VIDEO_NUM_CLR, + ANALOG_PLL_VIDEO_NUM_TOG, + ANALOG_PLL_VIDEO_DENOM, + ANALOG_PLL_VIDEO_DENOM_SET, + ANALOG_PLL_VIDEO_DENOM_CLR, + ANALOG_PLL_VIDEO_DENOM_TOG, + ANALOG_PLL_MISC0, + ANALOG_PLL_MISC0_SET, + ANALOG_PLL_MISC0_CLR, + ANALOG_PLL_MISC0_TOG, + + ANALOG_DIGPROG = 0x800 / sizeof(uint32_t), + ANALOG_MAX, + + ANALOG_PLL_LOCK = BIT(31) +}; + +enum IMX7CCMRegisters { + CCM_MAX = 0xBE00 / sizeof(uint32_t) + 1, +}; + +enum IMX7PMURegisters { + PMU_MAX = 0x140 / sizeof(uint32_t), +}; + +#define TYPE_IMX7_CCM "imx7.ccm" +#define IMX7_CCM(obj) OBJECT_CHECK(IMX7CCMState, (obj), TYPE_IMX7_CCM) + +typedef struct IMX7CCMState { + /* */ + IMXCCMState parent_obj; + + /* */ + MemoryRegion iomem; + + uint32_t ccm[CCM_MAX]; +} IMX7CCMState; + + +#define TYPE_IMX7_ANALOG "imx7.analog" +#define IMX7_ANALOG(obj) OBJECT_CHECK(IMX7AnalogState, (obj), TYPE_IMX7_ANALOG) + +typedef struct IMX7AnalogState { + /* */ + IMXCCMState parent_obj; + + /* */ + struct { + MemoryRegion container; + MemoryRegion analog; + MemoryRegion digprog; + MemoryRegion pmu; + } mmio; + + uint32_t analog[ANALOG_MAX]; + uint32_t pmu[PMU_MAX]; +} IMX7AnalogState; + +#endif /* IMX7_CCM_H */