diff options
author | Jean-Christophe DUBOIS <jcd@tribudubois.net> | 2016-05-12 13:22:28 +0100 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2016-05-12 13:22:28 +0100 |
commit | 1983057470e719d83ed4bc0fd48e480bfaf09d8a (patch) | |
tree | c48216619365afad38902012180a6571014ccd27 /hw/misc/imx6_src.c | |
parent | 825482adde1f971cbddf27e15fb4453ab3fae994 (diff) |
i.MX: Add i.MX6 System Reset Controller device.
This controller is also present in i.MX5X devices but they are not
yet emulated by QEMU.
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Jean-Christophe Dubois <jcd@tribudubois.net>
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Diffstat (limited to 'hw/misc/imx6_src.c')
-rw-r--r-- | hw/misc/imx6_src.c | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/hw/misc/imx6_src.c b/hw/misc/imx6_src.c new file mode 100644 index 0000000000..6b026b459f --- /dev/null +++ b/hw/misc/imx6_src.c @@ -0,0 +1,264 @@ +/* + * IMX6 System Reset Controller + * + * Copyright (c) 2015 Jean-Christophe Dubois <jcd@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 "qemu/osdep.h" +#include "hw/misc/imx6_src.h" +#include "sysemu/sysemu.h" +#include "qemu/bitops.h" +#include "arm-powerctl.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"); + + memset(s->regs, 0, sizeof(s->regs)); + + /* Set reset values */ + s->regs[SRC_SCR] = 0x521; + s->regs[SRC_SRSR] = 0x1; + s->regs[SRC_SIMR] = 0x1F; +} + +static uint64_t imx6_src_read(void *opaque, hwaddr offset, unsigned size) +{ + uint32_t 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 value; +} + +static void imx6_src_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + IMX6SRCState *s = (IMX6SRCState *)opaque; + uint32_t index = offset >> 2; + unsigned long change_mask; + unsigned long current_value = value; + + 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)current_value); + + change_mask = s->regs[index] ^ (uint32_t)current_value; + + switch (index) { + case SRC_SCR: + /* + * On real hardware when the system reset controller starts a + * secondary CPU it runs through some boot ROM code which reads + * the SRC_GPRX registers controlling the start address and branches + * to it. + * Here we are taking a short cut and branching directly to the + * requested address (we don't want to run the boot ROM code inside + * QEMU) + */ + if (EXTRACT(change_mask, CORE3_ENABLE)) { + if (EXTRACT(current_value, CORE3_ENABLE)) { + /* CORE 3 is brought up */ + arm_set_cpu_on(3, s->regs[SRC_GPR7], s->regs[SRC_GPR8], + 3, false); + } else { + /* CORE 3 is shut down */ + arm_set_cpu_off(3); + } + /* We clear the reset bits as the processor changed state */ + clear_bit(CORE3_RST_SHIFT, ¤t_value); + clear_bit(CORE3_RST_SHIFT, &change_mask); + } + if (EXTRACT(change_mask, CORE2_ENABLE)) { + if (EXTRACT(current_value, CORE2_ENABLE)) { + /* CORE 2 is brought up */ + arm_set_cpu_on(2, s->regs[SRC_GPR5], s->regs[SRC_GPR6], + 3, false); + } else { + /* CORE 3 is shut down */ + arm_set_cpu_off(2); + } + /* We clear the reset bits as the processor changed state */ + clear_bit(CORE2_RST_SHIFT, ¤t_value); + clear_bit(CORE2_RST_SHIFT, &change_mask); + } + if (EXTRACT(change_mask, CORE1_ENABLE)) { + if (EXTRACT(current_value, CORE1_ENABLE)) { + /* CORE 1 is brought up */ + arm_set_cpu_on(1, s->regs[SRC_GPR3], s->regs[SRC_GPR4], + 3, false); + } else { + /* CORE 3 is shut down */ + arm_set_cpu_off(1); + } + /* We clear the reset bits as the processor changed state */ + clear_bit(CORE1_RST_SHIFT, ¤t_value); + clear_bit(CORE1_RST_SHIFT, &change_mask); + } + if (EXTRACT(change_mask, CORE0_RST)) { + arm_reset_cpu(0); + clear_bit(CORE0_RST_SHIFT, ¤t_value); + } + if (EXTRACT(change_mask, CORE1_RST)) { + arm_reset_cpu(1); + clear_bit(CORE1_RST_SHIFT, ¤t_value); + } + if (EXTRACT(change_mask, CORE2_RST)) { + arm_reset_cpu(2); + clear_bit(CORE2_RST_SHIFT, ¤t_value); + } + if (EXTRACT(change_mask, CORE3_RST)) { + arm_reset_cpu(3); + clear_bit(CORE3_RST_SHIFT, ¤t_value); + } + if (EXTRACT(change_mask, SW_IPU2_RST)) { + /* We pretend the IPU2 is reset */ + clear_bit(SW_IPU2_RST_SHIFT, ¤t_value); + } + if (EXTRACT(change_mask, SW_IPU1_RST)) { + /* We pretend the IPU1 is reset */ + clear_bit(SW_IPU1_RST_SHIFT, ¤t_value); + } + s->regs[index] = current_value; + break; + default: + s->regs[index] = current_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) |