This controller is also present in i.MX5X devices but they are not yet emulated by Qemu.
Signed-off-by: Jean-Christophe Dubois <j...@tribudubois.net> --- hw/misc/Makefile.objs | 1 + hw/misc/imx6_src.c | 353 +++++++++++++++++++++++++++++++++++++++++++++ include/hw/misc/imx6_src.h | 72 +++++++++ 3 files changed, 426 insertions(+) create mode 100644 hw/misc/imx6_src.c create mode 100644 include/hw/misc/imx6_src.h diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs index a2a8e91..6bac654 100644 --- a/hw/misc/Makefile.objs +++ b/hw/misc/Makefile.objs @@ -29,6 +29,7 @@ obj-$(CONFIG_IMX) += imx_ccm.o 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_MILKYMIST) += milkymist-hpdmc.o obj-$(CONFIG_MILKYMIST) += milkymist-pfpu.o obj-$(CONFIG_MAINSTONE) += mst_fpga.o diff --git a/hw/misc/imx6_src.c b/hw/misc/imx6_src.c new file mode 100644 index 0000000..d1be7c1 --- /dev/null +++ b/hw/misc/imx6_src.c @@ -0,0 +1,353 @@ +/* + * IMX6 System Reset Controller + * + * Copyright (c) 2015 Jean-Christophe Dubois <j...@tribudubois.net> + * + * 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 "hw/misc/imx6_src.h" +#include "sysemu/sysemu.h" +#include "qemu/bitops.h" + +#ifndef DEBUG_IMX6_SRC +#define DEBUG_IMX6_SRC 0 +#endif + +#define DPRINTF(fmt, args...) \ + do { \ + if (DEBUG_IMX6_SRC) { \ + fprintf(stderr, "[%s]%s: " fmt , TYPE_IMX6_SRC, \ + __func__, ##args); \ + } \ + } while (0) + +static char const *imx6_src_reg_name(uint32_t reg) +{ + static char unknown[20]; + + switch (reg) { + case SRC_SCR: + return "SRC_SCR"; + case SRC_SBMR1: + return "SRC_SBMR1"; + case SRC_SRSR: + return "SRC_SRSR"; + case SRC_SISR: + return "SRC_SISR"; + case SRC_SIMR: + return "SRC_SIMR"; + case SRC_SBMR2: + return "SRC_SBMR2"; + case SRC_GPR1: + return "SRC_GPR1"; + case SRC_GPR2: + return "SRC_GPR2"; + case SRC_GPR3: + return "SRC_GPR3"; + case SRC_GPR4: + return "SRC_GPR4"; + case SRC_GPR5: + return "SRC_GPR5"; + case SRC_GPR6: + return "SRC_GPR6"; + case SRC_GPR7: + return "SRC_GPR7"; + case SRC_GPR8: + return "SRC_GPR8"; + case SRC_GPR9: + return "SRC_GPR9"; + case SRC_GPR10: + return "SRC_GPR10"; + default: + sprintf(unknown, "%d ?", reg); + return unknown; + } +} + +static const VMStateDescription vmstate_imx6_src = { + .name = TYPE_IMX6_SRC, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, IMX6SRCState, SRC_MAX), + VMSTATE_END_OF_LIST() + }, +}; + +static void imx6_src_reset(DeviceState *dev) +{ + IMX6SRCState *s = IMX6_SRC(dev); + + DPRINTF("\n"); + + /* + * We only clear the first registers as all GPR registers are preserved + * over resets + */ + memset(s->regs, 0, SRC_GPR1 * sizeof(uint32_t)); + + /* Set reset values */ + s->regs[SRC_SCR] = 0x521; + s->regs[SRC_SRSR] = 0x1; + s->regs[SRC_SIMR] = 0x1F; +} + +static CPUState *imx6_src_get_cpu_by_id(uint32_t id) +{ + CPUState *cpu; + + DPRINTF("cpu %d\n", id); + + CPU_FOREACH(cpu) { + ARMCPU *armcpu = ARM_CPU(cpu); + + if (armcpu->mp_affinity == id) { + return cpu; + } + } + + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Resquesting unknown CPU %d\n", + TYPE_IMX6_SRC, __func__, id); + + return NULL; +} + +static void imx6_src_cpu_on(uint32_t cpuid, uint32_t entry, uint32_t context_id) +{ + CPUState *target_cpu_state; + ARMCPU *target_cpu; + CPUClass *target_cpu_class; + + DPRINTF("cpu %d @ 0x%08x with R0 = 0x%08x\n", cpuid, entry, context_id); + + /* change to the cpu we are powering up */ + target_cpu_state = imx6_src_get_cpu_by_id(cpuid); + if (!target_cpu_state) { + return; + } + target_cpu = ARM_CPU(target_cpu_state); + if (!target_cpu->powered_off) { + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: CPU %d is already running\n", + TYPE_IMX6_SRC, __func__, cpuid); + return; + } + target_cpu_class = CPU_GET_CLASS(target_cpu); + + /* Initialize the cpu we are turning on */ + cpu_reset(target_cpu_state); + target_cpu->powered_off = false; + target_cpu_state->halted = 0; + + target_cpu->env.regs[0] = context_id; + target_cpu->env.thumb = entry & 1; + + target_cpu_class->set_pc(target_cpu_state, entry); +} + +static void imx6_src_cpu_off(uint32_t cpuid) +{ + CPUState *target_cpu_state; + ARMCPU *target_cpu; + + DPRINTF("cpu %d\n", cpuid); + + /* change to the cpu we are powering up */ + target_cpu_state = imx6_src_get_cpu_by_id(cpuid); + if (!target_cpu_state) { + return; + } + target_cpu = ARM_CPU(target_cpu_state); + if (target_cpu->powered_off) { + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: CPU %d is already off\n", + TYPE_IMX6_SRC, __func__, cpuid); + return; + } + + target_cpu->powered_off = true; + target_cpu_state->halted = 1; + target_cpu_state->exception_index = EXCP_HLT; + cpu_loop_exit(target_cpu_state); +} + +static void imx6_src_cpu_reset(uint32_t cpuid) +{ + CPUState *target_cpu_state; + ARMCPU *target_cpu; + + DPRINTF("cpu %d\n", cpuid); + + /* change to the cpu we are powering up */ + target_cpu_state = imx6_src_get_cpu_by_id(cpuid); + if (!target_cpu_state) { + return; + } + target_cpu = ARM_CPU(target_cpu_state); + if (target_cpu->powered_off) { + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: CPU %d is off\n", + TYPE_IMX6_SRC, __func__, cpuid); + return; + } + + /* Reset the cpu we are turning on */ + cpu_reset(target_cpu_state); +} + +static uint64_t imx6_src_read(void *opaque, hwaddr offset, unsigned size) +{ + uint32 value = 0; + IMX6SRCState *s = (IMX6SRCState *)opaque; + uint32_t index = offset >> 2; + + if (index < SRC_MAX) { + value = s->regs[index]; + } else { + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%" + HWADDR_PRIx "\n", TYPE_IMX6_SRC, __func__, offset); + + } + + DPRINTF("reg[%s] => 0x%" PRIx32 "\n", imx6_src_reg_name(index), value); + + return (uint64_t)value; +} + +static void imx6_src_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + IMX6SRCState *s = (IMX6SRCState *)opaque; + uint32_t index = offset >> 2; + uint64_t change_mask; + + if (index >= SRC_MAX) { + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%" + HWADDR_PRIx "\n", TYPE_IMX6_SRC, __func__, offset); + return; + } + + DPRINTF("reg[%s] <= 0x%" PRIx32 "\n", imx6_src_reg_name(index), + (uint32_t)value); + + change_mask = s->regs[index] ^ (uint32_t)value; + + switch (index) { + case SRC_SCR: + if (EXTRACT(change_mask, CORE3_ENABLE)) { + if (EXTRACT(value, CORE3_ENABLE)) { + /* CORE 3 is brought up */ + imx6_src_cpu_on(3, s->regs[SRC_GPR7], s->regs[SRC_GPR8]); + } else { + /* CORE 3 is shut down */ + imx6_src_cpu_off(3); + } + /* We clear the reset bits as the processor chaged state */ + clear_bit(CORE3_RST_SHIFT, &value); + clear_bit(CORE3_RST_SHIFT, &change_mask); + } + if (EXTRACT(change_mask, CORE2_ENABLE)) { + if (EXTRACT(value, CORE2_ENABLE)) { + /* CORE 2 is brought up */ + imx6_src_cpu_on(2, s->regs[SRC_GPR5], s->regs[SRC_GPR6]); + } else { + /* CORE 3 is shut down */ + imx6_src_cpu_off(2); + } + /* We clear the reset bits as the processor chaged state */ + clear_bit(CORE2_RST_SHIFT, &value); + clear_bit(CORE2_RST_SHIFT, &change_mask); + } + if (EXTRACT(change_mask, CORE1_ENABLE)) { + if (EXTRACT(value, CORE1_ENABLE)) { + /* CORE 1 is brought up */ + imx6_src_cpu_on(1, s->regs[SRC_GPR3], s->regs[SRC_GPR4]); + } else { + /* CORE 3 is shut down */ + imx6_src_cpu_off(1); + } + /* We clear the reset bits as the processor chaged state */ + clear_bit(CORE1_RST_SHIFT, &value); + clear_bit(CORE1_RST_SHIFT, &change_mask); + } + if (EXTRACT(change_mask, CORE0_RST)) { + imx6_src_cpu_reset(0); + clear_bit(CORE0_RST_SHIFT, &value); + } + if (EXTRACT(change_mask, CORE1_RST)) { + imx6_src_cpu_reset(1); + clear_bit(CORE1_RST_SHIFT, &value); + } + if (EXTRACT(change_mask, CORE2_RST)) { + imx6_src_cpu_reset(2); + clear_bit(CORE2_RST_SHIFT, &value); + } + if (EXTRACT(change_mask, CORE3_RST)) { + imx6_src_cpu_reset(3); + clear_bit(CORE3_RST_SHIFT, &value); + } + if (EXTRACT(change_mask, SW_IPU2_RST)) { + /* We pretend the IPU2 is reseted */ + clear_bit(SW_IPU2_RST_SHIFT, &value); + } + if (EXTRACT(change_mask, SW_IPU1_RST)) { + /* We pretend the IPU1 is reseted */ + clear_bit(SW_IPU1_RST_SHIFT, &value); + } + s->regs[index] = value; + break; + default: + s->regs[index] = value; + break; + } +} + +static const struct MemoryRegionOps imx6_src_ops = { + .read = imx6_src_read, + .write = imx6_src_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + /* + * 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 void imx6_src_realize(DeviceState *dev, Error **errp) +{ + IMX6SRCState *s = IMX6_SRC(dev); + + memory_region_init_io(&s->iomem, OBJECT(dev), &imx6_src_ops, s, + TYPE_IMX6_SRC, 0x1000); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem); +} + +static void imx6_src_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = imx6_src_realize; + dc->reset = imx6_src_reset; + dc->vmsd = &vmstate_imx6_src; + dc->desc = "i.MX6 System Reset Controller"; +} + +static const TypeInfo imx6_src_info = { + .name = TYPE_IMX6_SRC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IMX6SRCState), + .class_init = imx6_src_class_init, +}; + +static void imx6_src_register_types(void) +{ + type_register_static(&imx6_src_info); +} + +type_init(imx6_src_register_types) diff --git a/include/hw/misc/imx6_src.h b/include/hw/misc/imx6_src.h new file mode 100644 index 0000000..f3e0175 --- /dev/null +++ b/include/hw/misc/imx6_src.h @@ -0,0 +1,72 @@ +/* + * IMX6 System Reset Controller + * + * Copyright (C) 2012 NICTA + * Updated by Jean-Christophe Dubois <j...@tribudubois.net> + * + * 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 IMX6_SRC_H +#define IMX6_SRC_H + +#include "hw/sysbus.h" + +#define SRC_SCR 0 +#define SRC_SBMR1 1 +#define SRC_SRSR 2 +#define SRC_SISR 5 +#define SRC_SIMR 6 +#define SRC_SBMR2 7 +#define SRC_GPR1 8 +#define SRC_GPR2 9 +#define SRC_GPR3 10 +#define SRC_GPR4 11 +#define SRC_GPR5 12 +#define SRC_GPR6 13 +#define SRC_GPR7 14 +#define SRC_GPR8 15 +#define SRC_GPR9 16 +#define SRC_GPR10 17 +#define SRC_MAX 18 + +/* SRC_SCR */ +#define CORE3_ENABLE_SHIFT (24) +#define CORE3_ENABLE_MASK (0x1) +#define CORE2_ENABLE_SHIFT (23) +#define CORE2_ENABLE_MASK (0x1) +#define CORE1_ENABLE_SHIFT (22) +#define CORE1_ENABLE_MASK (0x1) +#define CORE3_RST_SHIFT (16) +#define CORE3_RST_MASK (0x1) +#define CORE2_RST_SHIFT (15) +#define CORE2_RST_MASK (0x1) +#define CORE1_RST_SHIFT (14) +#define CORE1_RST_MASK (0x1) +#define CORE0_RST_SHIFT (13) +#define CORE0_RST_MASK (0x1) +#define SW_IPU1_RST_SHIFT (3) +#define SW_IPU1_RST_MASK (0x1) +#define SW_IPU2_RST_SHIFT (12) +#define SW_IPU2_RST_MASK (0x1) +#define WARM_RST_ENABLE_SHIFT (0) +#define WARM_RST_ENABLE_MASK (0x1) + +#define EXTRACT(value, name) (((value) >> name##_SHIFT) & name##_MASK) + +#define TYPE_IMX6_SRC "imx6.src" +#define IMX6_SRC(obj) OBJECT_CHECK(IMX6SRCState, (obj), TYPE_IMX6_SRC) + +typedef struct IMX6SRCState { + /* <private> */ + SysBusDevice parent_obj; + + /* <public> */ + MemoryRegion iomem; + + uint32_t regs[SRC_MAX]; + +} IMX6SRCState; + +#endif /* IMX6_SRC_H */ -- 2.5.0