From 8e03cf1eeb1f9e06d281db1d93f92eb25f346f15 Mon Sep 17 00:00:00 2001 From: Evgeny Voevodin Date: Thu, 16 Feb 2012 09:56:04 +0000 Subject: ARM: exynos4210: IRQ subsystem support. Signed-off-by: Evgeny Voevodin Signed-off-by: Peter Maydell --- hw/exynos4210.h | 82 +++++++++ hw/exynos4210_combiner.c | 469 +++++++++++++++++++++++++++++++++++++++++++++++ hw/exynos4210_gic.c | 458 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1009 insertions(+) create mode 100644 hw/exynos4210.h create mode 100644 hw/exynos4210_combiner.c create mode 100644 hw/exynos4210_gic.c (limited to 'hw') diff --git a/hw/exynos4210.h b/hw/exynos4210.h new file mode 100644 index 0000000..ef4732f --- /dev/null +++ b/hw/exynos4210.h @@ -0,0 +1,82 @@ +/* + * Samsung exynos4210 SoC emulation + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. All rights reserved. + * Maksim Kozlov + * Evgeny Voevodin + * Igor Mitsyanko + * + * + * 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 . + * + */ + + +#ifndef EXYNOS4210_H_ +#define EXYNOS4210_H_ + +#include "qemu-common.h" +#include "memory.h" + +#define EXYNOS4210_NCPUS 2 + +/* + * exynos4210 IRQ subsystem stub definitions. + */ +#define EXYNOS4210_IRQ_GATE_NINPUTS 8 + +#define EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ 64 +#define EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ 16 +#define EXYNOS4210_MAX_INT_COMBINER_IN_IRQ \ + (EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ * 8) +#define EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ \ + (EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ * 8) + +#define EXYNOS4210_COMBINER_GET_IRQ_NUM(grp, bit) ((grp)*8 + (bit)) +#define EXYNOS4210_COMBINER_GET_GRP_NUM(irq) ((irq) / 8) +#define EXYNOS4210_COMBINER_GET_BIT_NUM(irq) \ + ((irq) - 8 * EXYNOS4210_COMBINER_GET_GRP_NUM(irq)) + +/* IRQs number for external and internal GIC */ +#define EXYNOS4210_EXT_GIC_NIRQ (160-32) +#define EXYNOS4210_INT_GIC_NIRQ 64 + +typedef struct Exynos4210Irq { + qemu_irq int_combiner_irq[EXYNOS4210_MAX_INT_COMBINER_IN_IRQ]; + qemu_irq ext_combiner_irq[EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ]; + qemu_irq int_gic_irq[EXYNOS4210_INT_GIC_NIRQ]; + qemu_irq ext_gic_irq[EXYNOS4210_EXT_GIC_NIRQ]; + qemu_irq board_irqs[EXYNOS4210_MAX_INT_COMBINER_IN_IRQ]; +} Exynos4210Irq; + +/* Initialize exynos4210 IRQ subsystem stub */ +qemu_irq *exynos4210_init_irq(Exynos4210Irq *env); + +/* Initialize board IRQs. + * These IRQs contain splitted Int/External Combiner and External Gic IRQs */ +void exynos4210_init_board_irqs(Exynos4210Irq *s); + +/* Get IRQ number from exynos4210 IRQ subsystem stub. + * To identify IRQ source use internal combiner group and bit number + * grp - group number + * bit - bit number inside group */ +uint32_t exynos4210_get_irq(uint32_t grp, uint32_t bit); + +/* + * Get Combiner input GPIO into irqs structure + */ +void exynos4210_combiner_get_gpioin(Exynos4210Irq *irqs, DeviceState *dev, + int ext); + +#endif /* EXYNOS4210_H_ */ diff --git a/hw/exynos4210_combiner.c b/hw/exynos4210_combiner.c new file mode 100644 index 0000000..6110c19 --- /dev/null +++ b/hw/exynos4210_combiner.c @@ -0,0 +1,469 @@ +/* + * Samsung exynos4210 Interrupt Combiner + * + * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. + * All rights reserved. + * + * Evgeny Voevodin + * + * 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 . + */ + +/* + * Exynos4210 Combiner represents an OR gate for SOC's IRQ lines. It combines + * IRQ sources into groups and provides signal output to GIC from each group. It + * is driven by common mask and enable/disable logic. Take a note that not all + * IRQs are passed to GIC through Combiner. + */ + +#include "sysbus.h" + +#include "exynos4210.h" + +//#define DEBUG_COMBINER + +#ifdef DEBUG_COMBINER +#define DPRINTF(fmt, ...) \ + do { fprintf(stdout, "COMBINER: [%s:%d] " fmt, __func__ , __LINE__, \ + ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do {} while (0) +#endif + +#define IIC_NGRP 64 /* Internal Interrupt Combiner + Groups number */ +#define IIC_NIRQ (IIC_NGRP * 8)/* Internal Interrupt Combiner + Interrupts number */ +#define IIC_REGION_SIZE 0x108 /* Size of memory mapped region */ +#define IIC_REGSET_SIZE 0x41 + +/* + * State for each output signal of internal combiner + */ +typedef struct CombinerGroupState { + uint8_t src_mask; /* 1 - source enabled, 0 - disabled */ + uint8_t src_pending; /* Pending source interrupts before masking */ +} CombinerGroupState; + +typedef struct Exynos4210CombinerState { + SysBusDevice busdev; + MemoryRegion iomem; + + struct CombinerGroupState group[IIC_NGRP]; + uint32_t reg_set[IIC_REGSET_SIZE]; + uint32_t icipsr[2]; + uint32_t external; /* 1 means that this combiner is external */ + + qemu_irq output_irq[IIC_NGRP]; +} Exynos4210CombinerState; + +static const VMStateDescription vmstate_exynos4210_combiner_group_state = { + .name = "exynos4210.combiner.groupstate", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(src_mask, CombinerGroupState), + VMSTATE_UINT8(src_pending, CombinerGroupState), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_exynos4210_combiner = { + .name = "exynos4210.combiner", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT_ARRAY(group, Exynos4210CombinerState, IIC_NGRP, 0, + vmstate_exynos4210_combiner_group_state, CombinerGroupState), + VMSTATE_UINT32_ARRAY(reg_set, Exynos4210CombinerState, + IIC_REGSET_SIZE), + VMSTATE_UINT32_ARRAY(icipsr, Exynos4210CombinerState, 2), + VMSTATE_UINT32(external, Exynos4210CombinerState), + VMSTATE_END_OF_LIST() + } +}; + +/* + * Get Combiner input GPIO into irqs structure + */ +void exynos4210_combiner_get_gpioin(Exynos4210Irq *irqs, DeviceState *dev, + int ext) +{ + int n; + int bit; + int max; + qemu_irq *irq; + + max = ext ? EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ : + EXYNOS4210_MAX_INT_COMBINER_IN_IRQ; + irq = ext ? irqs->ext_combiner_irq : irqs->int_combiner_irq; + + /* + * Some IRQs of Int/External Combiner are going to two Combiners groups, + * so let split them. + */ + for (n = 0; n < max; n++) { + + bit = EXYNOS4210_COMBINER_GET_BIT_NUM(n); + + switch (n) { + /* MDNIE_LCD1 INTG1 */ + case EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 0) ... + EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 3): + irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), + irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(0, bit + 4)]); + continue; + + /* TMU INTG3 */ + case EXYNOS4210_COMBINER_GET_IRQ_NUM(3, 4): + irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), + irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(2, bit)]); + continue; + + /* LCD1 INTG12 */ + case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 0) ... + EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 3): + irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), + irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(11, bit + 4)]); + continue; + + /* Multi-Core Timer INTG12 */ + case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 4) ... + EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 8): + irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), + irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]); + continue; + + /* Multi-Core Timer INTG35 */ + case EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 4) ... + EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 8): + irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), + irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]); + continue; + + /* Multi-Core Timer INTG51 */ + case EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 4) ... + EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 8): + irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), + irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]); + continue; + + /* Multi-Core Timer INTG53 */ + case EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 4) ... + EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 8): + irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), + irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]); + continue; + } + + irq[n] = qdev_get_gpio_in(dev, n); + } +} + +static uint64_t +exynos4210_combiner_read(void *opaque, target_phys_addr_t offset, unsigned size) +{ + struct Exynos4210CombinerState *s = + (struct Exynos4210CombinerState *)opaque; + uint32_t req_quad_base_n; /* Base of registers quad. Multiply it by 4 and + get a start of corresponding group quad */ + uint32_t grp_quad_base_n; /* Base of group quad */ + uint32_t reg_n; /* Register number inside the quad */ + uint32_t val; + + if (s->external && (offset > 0x3c && offset != 0x100)) { + hw_error("exynos4210.combiner: unallowed read access at offset 0x" + TARGET_FMT_plx "\n", offset); + } + + req_quad_base_n = offset >> 4; + grp_quad_base_n = req_quad_base_n << 2; + reg_n = (offset - (req_quad_base_n << 4)) >> 2; + + if (req_quad_base_n >= IIC_NGRP) { + /* Read of ICIPSR register */ + return s->icipsr[reg_n]; + } + + val = 0; + + switch (reg_n) { + /* IISTR */ + case 2: + val |= s->group[grp_quad_base_n].src_pending; + val |= s->group[grp_quad_base_n + 1].src_pending << 8; + val |= s->group[grp_quad_base_n + 2].src_pending << 16; + val |= s->group[grp_quad_base_n + 3].src_pending << 24; + break; + /* IIMSR */ + case 3: + val |= s->group[grp_quad_base_n].src_mask & + s->group[grp_quad_base_n].src_pending; + val |= (s->group[grp_quad_base_n + 1].src_mask & + s->group[grp_quad_base_n + 1].src_pending) << 8; + val |= (s->group[grp_quad_base_n + 2].src_mask & + s->group[grp_quad_base_n + 2].src_pending) << 16; + val |= (s->group[grp_quad_base_n + 3].src_mask & + s->group[grp_quad_base_n + 3].src_pending) << 24; + break; + default: + if (offset >> 2 >= IIC_REGSET_SIZE) { + hw_error("exynos4210.combiner: overflow of reg_set by 0x" + TARGET_FMT_plx "offset\n", offset); + } + val = s->reg_set[offset >> 2]; + return 0; + } + return val; +} + +static void exynos4210_combiner_update(void *opaque, uint8_t group_n) +{ + struct Exynos4210CombinerState *s = + (struct Exynos4210CombinerState *)opaque; + + /* Send interrupt if needed */ + if (s->group[group_n].src_mask & s->group[group_n].src_pending) { +#ifdef DEBUG_COMBINER + if (group_n != 26) { + /* skip uart */ + DPRINTF("%s raise IRQ[%d]\n", s->external ? "EXT" : "INT", group_n); + } +#endif + + /* Set Combiner interrupt pending status after masking */ + if (group_n >= 32) { + s->icipsr[1] |= 1 << (group_n - 32); + } else { + s->icipsr[0] |= 1 << group_n; + } + + qemu_irq_raise(s->output_irq[group_n]); + } else { +#ifdef DEBUG_COMBINER + if (group_n != 26) { + /* skip uart */ + DPRINTF("%s lower IRQ[%d]\n", s->external ? "EXT" : "INT", group_n); + } +#endif + + /* Set Combiner interrupt pending status after masking */ + if (group_n >= 32) { + s->icipsr[1] &= ~(1 << (group_n - 32)); + } else { + s->icipsr[0] &= ~(1 << group_n); + } + + qemu_irq_lower(s->output_irq[group_n]); + } +} + +static void exynos4210_combiner_write(void *opaque, target_phys_addr_t offset, + uint64_t val, unsigned size) +{ + struct Exynos4210CombinerState *s = + (struct Exynos4210CombinerState *)opaque; + uint32_t req_quad_base_n; /* Base of registers quad. Multiply it by 4 and + get a start of corresponding group quad */ + uint32_t grp_quad_base_n; /* Base of group quad */ + uint32_t reg_n; /* Register number inside the quad */ + + if (s->external && (offset > 0x3c && offset != 0x100)) { + hw_error("exynos4210.combiner: unallowed write access at offset 0x" + TARGET_FMT_plx "\n", offset); + } + + req_quad_base_n = offset >> 4; + grp_quad_base_n = req_quad_base_n << 2; + reg_n = (offset - (req_quad_base_n << 4)) >> 2; + + if (req_quad_base_n >= IIC_NGRP) { + hw_error("exynos4210.combiner: unallowed write access at offset 0x" + TARGET_FMT_plx "\n", offset); + return; + } + + if (reg_n > 1) { + hw_error("exynos4210.combiner: unallowed write access at offset 0x" + TARGET_FMT_plx "\n", offset); + return; + } + + if (offset >> 2 >= IIC_REGSET_SIZE) { + hw_error("exynos4210.combiner: overflow of reg_set by 0x" + TARGET_FMT_plx "offset\n", offset); + } + s->reg_set[offset >> 2] = val; + + switch (reg_n) { + /* IIESR */ + case 0: + /* FIXME: what if irq is pending, allowed by mask, and we allow it + * again. Interrupt will rise again! */ + + DPRINTF("%s enable IRQ for groups %d, %d, %d, %d\n", + s->external ? "EXT" : "INT", + grp_quad_base_n, + grp_quad_base_n + 1, + grp_quad_base_n + 2, + grp_quad_base_n + 3); + + /* Enable interrupt sources */ + s->group[grp_quad_base_n].src_mask |= val & 0xFF; + s->group[grp_quad_base_n + 1].src_mask |= (val & 0xFF00) >> 8; + s->group[grp_quad_base_n + 2].src_mask |= (val & 0xFF0000) >> 16; + s->group[grp_quad_base_n + 3].src_mask |= (val & 0xFF000000) >> 24; + + exynos4210_combiner_update(s, grp_quad_base_n); + exynos4210_combiner_update(s, grp_quad_base_n + 1); + exynos4210_combiner_update(s, grp_quad_base_n + 2); + exynos4210_combiner_update(s, grp_quad_base_n + 3); + break; + /* IIECR */ + case 1: + DPRINTF("%s disable IRQ for groups %d, %d, %d, %d\n", + s->external ? "EXT" : "INT", + grp_quad_base_n, + grp_quad_base_n + 1, + grp_quad_base_n + 2, + grp_quad_base_n + 3); + + /* Disable interrupt sources */ + s->group[grp_quad_base_n].src_mask &= ~(val & 0xFF); + s->group[grp_quad_base_n + 1].src_mask &= ~((val & 0xFF00) >> 8); + s->group[grp_quad_base_n + 2].src_mask &= ~((val & 0xFF0000) >> 16); + s->group[grp_quad_base_n + 3].src_mask &= ~((val & 0xFF000000) >> 24); + + exynos4210_combiner_update(s, grp_quad_base_n); + exynos4210_combiner_update(s, grp_quad_base_n + 1); + exynos4210_combiner_update(s, grp_quad_base_n + 2); + exynos4210_combiner_update(s, grp_quad_base_n + 3); + break; + default: + hw_error("exynos4210.combiner: unallowed write access at offset 0x" + TARGET_FMT_plx "\n", offset); + break; + } + + return; +} + +/* Get combiner group and bit from irq number */ +static uint8_t get_combiner_group_and_bit(int irq, uint8_t *bit) +{ + *bit = irq - ((irq >> 3) << 3); + return irq >> 3; +} + +/* Process a change in an external IRQ input. */ +static void exynos4210_combiner_handler(void *opaque, int irq, int level) +{ + struct Exynos4210CombinerState *s = + (struct Exynos4210CombinerState *)opaque; + uint8_t bit_n, group_n; + + group_n = get_combiner_group_and_bit(irq, &bit_n); + + if (s->external && group_n >= EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ) { + DPRINTF("%s unallowed IRQ group 0x%x\n", s->external ? "EXT" : "INT" + , group_n); + return; + } + + if (level) { + s->group[group_n].src_pending |= 1 << bit_n; + } else { + s->group[group_n].src_pending &= ~(1 << bit_n); + } + + exynos4210_combiner_update(s, group_n); + + return; +} + +static void exynos4210_combiner_reset(DeviceState *d) +{ + struct Exynos4210CombinerState *s = (struct Exynos4210CombinerState *)d; + + memset(&s->group, 0, sizeof(s->group)); + memset(&s->reg_set, 0, sizeof(s->reg_set)); + + s->reg_set[0xC0 >> 2] = 0x01010101; + s->reg_set[0xC4 >> 2] = 0x01010101; + s->reg_set[0xD0 >> 2] = 0x01010101; + s->reg_set[0xD4 >> 2] = 0x01010101; +} + +static const MemoryRegionOps exynos4210_combiner_ops = { + .read = exynos4210_combiner_read, + .write = exynos4210_combiner_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +/* + * Internal Combiner initialization. + */ +static int exynos4210_combiner_init(SysBusDevice *dev) +{ + unsigned int i; + struct Exynos4210CombinerState *s = + FROM_SYSBUS(struct Exynos4210CombinerState, dev); + + /* Allocate general purpose input signals and connect a handler to each of + * them */ + qdev_init_gpio_in(&s->busdev.qdev, exynos4210_combiner_handler, IIC_NIRQ); + + /* Connect SysBusDev irqs to device specific irqs */ + for (i = 0; i < IIC_NIRQ; i++) { + sysbus_init_irq(dev, &s->output_irq[i]); + } + + memory_region_init_io(&s->iomem, &exynos4210_combiner_ops, s, + "exynos4210-combiner", IIC_REGION_SIZE); + sysbus_init_mmio(dev, &s->iomem); + + return 0; +} + +static Property exynos4210_combiner_properties[] = { + DEFINE_PROP_UINT32("external", Exynos4210CombinerState, external, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void exynos4210_combiner_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = exynos4210_combiner_init; + dc->reset = exynos4210_combiner_reset; + dc->props = exynos4210_combiner_properties; + dc->vmsd = &vmstate_exynos4210_combiner; +} + +static TypeInfo exynos4210_combiner_info = { + .name = "exynos4210.combiner", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Exynos4210CombinerState), + .class_init = exynos4210_combiner_class_init, +}; + +static void exynos4210_combiner_register_types(void) +{ + type_register_static(&exynos4210_combiner_info); +} + +type_init(exynos4210_combiner_register_types) diff --git a/hw/exynos4210_gic.c b/hw/exynos4210_gic.c new file mode 100644 index 0000000..ec13140 --- /dev/null +++ b/hw/exynos4210_gic.c @@ -0,0 +1,458 @@ +/* + * Samsung exynos4210 GIC implementation. Based on hw/arm_gic.c + * + * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. + * All rights reserved. + * + * Evgeny Voevodin + * + * 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 . + */ + +#include "sysbus.h" +#include "qemu-common.h" +#include "irq.h" +#include "exynos4210.h" + +enum ExtGicId { + EXT_GIC_ID_MDMA_LCD0 = 66, + EXT_GIC_ID_PDMA0, + EXT_GIC_ID_PDMA1, + EXT_GIC_ID_TIMER0, + EXT_GIC_ID_TIMER1, + EXT_GIC_ID_TIMER2, + EXT_GIC_ID_TIMER3, + EXT_GIC_ID_TIMER4, + EXT_GIC_ID_MCT_L0, + EXT_GIC_ID_WDT, + EXT_GIC_ID_RTC_ALARM, + EXT_GIC_ID_RTC_TIC, + EXT_GIC_ID_GPIO_XB, + EXT_GIC_ID_GPIO_XA, + EXT_GIC_ID_MCT_L1, + EXT_GIC_ID_IEM_APC, + EXT_GIC_ID_IEM_IEC, + EXT_GIC_ID_NFC, + EXT_GIC_ID_UART0, + EXT_GIC_ID_UART1, + EXT_GIC_ID_UART2, + EXT_GIC_ID_UART3, + EXT_GIC_ID_UART4, + EXT_GIC_ID_MCT_G0, + EXT_GIC_ID_I2C0, + EXT_GIC_ID_I2C1, + EXT_GIC_ID_I2C2, + EXT_GIC_ID_I2C3, + EXT_GIC_ID_I2C4, + EXT_GIC_ID_I2C5, + EXT_GIC_ID_I2C6, + EXT_GIC_ID_I2C7, + EXT_GIC_ID_SPI0, + EXT_GIC_ID_SPI1, + EXT_GIC_ID_SPI2, + EXT_GIC_ID_MCT_G1, + EXT_GIC_ID_USB_HOST, + EXT_GIC_ID_USB_DEVICE, + EXT_GIC_ID_MODEMIF, + EXT_GIC_ID_HSMMC0, + EXT_GIC_ID_HSMMC1, + EXT_GIC_ID_HSMMC2, + EXT_GIC_ID_HSMMC3, + EXT_GIC_ID_SDMMC, + EXT_GIC_ID_MIPI_CSI_4LANE, + EXT_GIC_ID_MIPI_DSI_4LANE, + EXT_GIC_ID_MIPI_CSI_2LANE, + EXT_GIC_ID_MIPI_DSI_2LANE, + EXT_GIC_ID_ONENAND_AUDI, + EXT_GIC_ID_ROTATOR, + EXT_GIC_ID_FIMC0, + EXT_GIC_ID_FIMC1, + EXT_GIC_ID_FIMC2, + EXT_GIC_ID_FIMC3, + EXT_GIC_ID_JPEG, + EXT_GIC_ID_2D, + EXT_GIC_ID_PCIe, + EXT_GIC_ID_MIXER, + EXT_GIC_ID_HDMI, + EXT_GIC_ID_HDMI_I2C, + EXT_GIC_ID_MFC, + EXT_GIC_ID_TVENC, +}; + +enum ExtInt { + EXT_GIC_ID_EXTINT0 = 48, + EXT_GIC_ID_EXTINT1, + EXT_GIC_ID_EXTINT2, + EXT_GIC_ID_EXTINT3, + EXT_GIC_ID_EXTINT4, + EXT_GIC_ID_EXTINT5, + EXT_GIC_ID_EXTINT6, + EXT_GIC_ID_EXTINT7, + EXT_GIC_ID_EXTINT8, + EXT_GIC_ID_EXTINT9, + EXT_GIC_ID_EXTINT10, + EXT_GIC_ID_EXTINT11, + EXT_GIC_ID_EXTINT12, + EXT_GIC_ID_EXTINT13, + EXT_GIC_ID_EXTINT14, + EXT_GIC_ID_EXTINT15 +}; + +/* + * External GIC sources which are not from External Interrupt Combiner or + * External Interrupts are starting from EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ, + * which is INTG16 in Internal Interrupt Combiner. + */ + +static uint32_t +combiner_grp_to_gic_id[64-EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ][8] = { + /* int combiner groups 16-19 */ + { }, { }, { }, { }, + /* int combiner group 20 */ + { 0, EXT_GIC_ID_MDMA_LCD0 }, + /* int combiner group 21 */ + { EXT_GIC_ID_PDMA0, EXT_GIC_ID_PDMA1 }, + /* int combiner group 22 */ + { EXT_GIC_ID_TIMER0, EXT_GIC_ID_TIMER1, EXT_GIC_ID_TIMER2, + EXT_GIC_ID_TIMER3, EXT_GIC_ID_TIMER4 }, + /* int combiner group 23 */ + { EXT_GIC_ID_RTC_ALARM, EXT_GIC_ID_RTC_TIC }, + /* int combiner group 24 */ + { EXT_GIC_ID_GPIO_XB, EXT_GIC_ID_GPIO_XA }, + /* int combiner group 25 */ + { EXT_GIC_ID_IEM_APC, EXT_GIC_ID_IEM_IEC }, + /* int combiner group 26 */ + { EXT_GIC_ID_UART0, EXT_GIC_ID_UART1, EXT_GIC_ID_UART2, EXT_GIC_ID_UART3, + EXT_GIC_ID_UART4 }, + /* int combiner group 27 */ + { EXT_GIC_ID_I2C0, EXT_GIC_ID_I2C1, EXT_GIC_ID_I2C2, EXT_GIC_ID_I2C3, + EXT_GIC_ID_I2C4, EXT_GIC_ID_I2C5, EXT_GIC_ID_I2C6, + EXT_GIC_ID_I2C7 }, + /* int combiner group 28 */ + { EXT_GIC_ID_SPI0, EXT_GIC_ID_SPI1, EXT_GIC_ID_SPI2 }, + /* int combiner group 29 */ + { EXT_GIC_ID_HSMMC0, EXT_GIC_ID_HSMMC1, EXT_GIC_ID_HSMMC2, + EXT_GIC_ID_HSMMC3, EXT_GIC_ID_SDMMC }, + /* int combiner group 30 */ + { EXT_GIC_ID_MIPI_CSI_4LANE, EXT_GIC_ID_MIPI_CSI_2LANE }, + /* int combiner group 31 */ + { EXT_GIC_ID_MIPI_DSI_4LANE, EXT_GIC_ID_MIPI_DSI_2LANE }, + /* int combiner group 32 */ + { EXT_GIC_ID_FIMC0, EXT_GIC_ID_FIMC1 }, + /* int combiner group 33 */ + { EXT_GIC_ID_FIMC2, EXT_GIC_ID_FIMC3 }, + /* int combiner group 34 */ + { EXT_GIC_ID_ONENAND_AUDI, EXT_GIC_ID_NFC }, + /* int combiner group 35 */ + { 0, 0, 0, EXT_GIC_ID_MCT_L1, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1 }, + /* int combiner group 36 */ + { EXT_GIC_ID_MIXER }, + /* int combiner group 37 */ + { EXT_GIC_ID_EXTINT4, EXT_GIC_ID_EXTINT5, EXT_GIC_ID_EXTINT6, + EXT_GIC_ID_EXTINT7 }, + /* groups 38-50 */ + { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, + /* int combiner group 51 */ + { EXT_GIC_ID_MCT_L0, 0, 0, 0, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1 }, + /* group 52 */ + { }, + /* int combiner group 53 */ + { EXT_GIC_ID_WDT, 0, 0, 0, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1 }, + /* groups 54-63 */ + { }, { }, { }, { }, { }, { }, { }, { }, { }, { } +}; + +#define EXYNOS4210_GIC_NIRQ 160 +#define NCPU EXYNOS4210_NCPUS + +#define EXYNOS4210_EXT_GIC_CPU_REGION_SIZE 0x10000 +#define EXYNOS4210_EXT_GIC_DIST_REGION_SIZE 0x10000 + +#define EXYNOS4210_EXT_GIC_PER_CPU_OFFSET 0x8000 +#define EXYNOS4210_EXT_GIC_CPU_GET_OFFSET(n) \ + ((n) * EXYNOS4210_EXT_GIC_PER_CPU_OFFSET) +#define EXYNOS4210_EXT_GIC_DIST_GET_OFFSET(n) \ + ((n) * EXYNOS4210_EXT_GIC_PER_CPU_OFFSET) + +#define EXYNOS4210_GIC_CPU_REGION_SIZE 0x100 +#define EXYNOS4210_GIC_DIST_REGION_SIZE 0x1000 + +static void exynos4210_irq_handler(void *opaque, int irq, int level) +{ + Exynos4210Irq *s = (Exynos4210Irq *)opaque; + + /* Bypass */ + qemu_set_irq(s->board_irqs[irq], level); + + return; +} + +/* + * Initialize exynos4210 IRQ subsystem stub. + */ +qemu_irq *exynos4210_init_irq(Exynos4210Irq *s) +{ + return qemu_allocate_irqs(exynos4210_irq_handler, s, + EXYNOS4210_MAX_INT_COMBINER_IN_IRQ); +} + +/* + * Initialize board IRQs. + * These IRQs contain splitted Int/External Combiner and External Gic IRQs. + */ +void exynos4210_init_board_irqs(Exynos4210Irq *s) +{ + uint32_t grp, bit, irq_id, n; + + for (n = 0; n < EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ; n++) { + s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n], + s->ext_combiner_irq[n]); + + irq_id = 0; + if (n == EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 4) || + n == EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 4)) { + /* MCT_G0 is passed to External GIC */ + irq_id = EXT_GIC_ID_MCT_G0; + } + if (n == EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 5) || + n == EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 5)) { + /* MCT_G1 is passed to External and GIC */ + irq_id = EXT_GIC_ID_MCT_G1; + } + if (irq_id) { + s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n], + s->ext_gic_irq[irq_id-32]); + } + + } + for (; n < EXYNOS4210_MAX_INT_COMBINER_IN_IRQ; n++) { + /* these IDs are passed to Internal Combiner and External GIC */ + grp = EXYNOS4210_COMBINER_GET_GRP_NUM(n); + bit = EXYNOS4210_COMBINER_GET_BIT_NUM(n); + irq_id = combiner_grp_to_gic_id[grp - + EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ][bit]; + + if (irq_id) { + s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n], + s->ext_gic_irq[irq_id-32]); + } + } +} + +/* + * Get IRQ number from exynos4210 IRQ subsystem stub. + * To identify IRQ source use internal combiner group and bit number + * grp - group number + * bit - bit number inside group + */ +uint32_t exynos4210_get_irq(uint32_t grp, uint32_t bit) +{ + return EXYNOS4210_COMBINER_GET_IRQ_NUM(grp, bit); +} + +/********* GIC part *********/ + +static inline int +gic_get_current_cpu(void) +{ + return cpu_single_env->cpu_index; +} + +#include "arm_gic.c" + +typedef struct { + gic_state gic; + MemoryRegion cpu_container; + MemoryRegion dist_container; + MemoryRegion cpu_alias[NCPU]; + MemoryRegion dist_alias[NCPU]; + uint32_t num_cpu; +} Exynos4210GicState; + +static int exynos4210_gic_init(SysBusDevice *dev) +{ + Exynos4210GicState *s = FROM_SYSBUSGIC(Exynos4210GicState, dev); + uint32_t i; + const char cpu_prefix[] = "exynos4210-gic-alias_cpu"; + const char dist_prefix[] = "exynos4210-gic-alias_dist"; + char cpu_alias_name[sizeof(cpu_prefix) + 3]; + char dist_alias_name[sizeof(cpu_prefix) + 3]; + + gic_init(&s->gic, s->num_cpu, EXYNOS4210_GIC_NIRQ); + + memory_region_init(&s->cpu_container, "exynos4210-cpu-container", + EXYNOS4210_EXT_GIC_CPU_REGION_SIZE); + memory_region_init(&s->dist_container, "exynos4210-dist-container", + EXYNOS4210_EXT_GIC_DIST_REGION_SIZE); + + for (i = 0; i < s->num_cpu; i++) { + /* Map CPU interface per SMP Core */ + sprintf(cpu_alias_name, "%s%x", cpu_prefix, i); + memory_region_init_alias(&s->cpu_alias[i], + cpu_alias_name, + &s->gic.cpuiomem[0], + 0, + EXYNOS4210_GIC_CPU_REGION_SIZE); + memory_region_add_subregion(&s->cpu_container, + EXYNOS4210_EXT_GIC_CPU_GET_OFFSET(i), &s->cpu_alias[i]); + + /* Map Distributor per SMP Core */ + sprintf(dist_alias_name, "%s%x", dist_prefix, i); + memory_region_init_alias(&s->dist_alias[i], + dist_alias_name, + &s->gic.iomem, + 0, + EXYNOS4210_GIC_DIST_REGION_SIZE); + memory_region_add_subregion(&s->dist_container, + EXYNOS4210_EXT_GIC_DIST_GET_OFFSET(i), &s->dist_alias[i]); + } + + sysbus_init_mmio(dev, &s->cpu_container); + sysbus_init_mmio(dev, &s->dist_container); + + gic_cpu_write(&s->gic, 1, 0, 1); + + return 0; +} + +static Property exynos4210_gic_properties[] = { + DEFINE_PROP_UINT32("num-cpu", Exynos4210GicState, num_cpu, 1), + DEFINE_PROP_END_OF_LIST(), +}; + +static void exynos4210_gic_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = exynos4210_gic_init; + dc->props = exynos4210_gic_properties; +} + +static TypeInfo exynos4210_gic_info = { + .name = "exynos4210.gic", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Exynos4210GicState), + .class_init = exynos4210_gic_class_init, +}; + +static void exynos4210_gic_register_types(void) +{ + type_register_static(&exynos4210_gic_info); +} + +type_init(exynos4210_gic_register_types) + +/* + * IRQGate struct. + * IRQ Gate represents OR gate between GICs to pass IRQ to PIC. + */ +typedef struct { + SysBusDevice busdev; + + qemu_irq pic_irq[NCPU]; /* output IRQs to PICs */ + uint32_t gpio_level[EXYNOS4210_IRQ_GATE_NINPUTS]; /* Input levels */ +} Exynos4210IRQGateState; + +static const VMStateDescription vmstate_exynos4210_irq_gate = { + .name = "exynos4210.irq_gate", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(gpio_level, Exynos4210IRQGateState, + EXYNOS4210_IRQ_GATE_NINPUTS), + VMSTATE_END_OF_LIST() + } +}; + +/* Process a change in an external IRQ input. */ +static void exynos4210_irq_gate_handler(void *opaque, int irq, int level) +{ + Exynos4210IRQGateState *s = + (Exynos4210IRQGateState *)opaque; + uint32_t odd, even; + + if (irq & 1) { + odd = irq; + even = irq & ~1; + } else { + even = irq; + odd = irq | 1; + } + + assert(irq < EXYNOS4210_IRQ_GATE_NINPUTS); + s->gpio_level[irq] = level; + + if (s->gpio_level[odd] >= 1 || s->gpio_level[even] >= 1) { + qemu_irq_raise(s->pic_irq[even >> 1]); + } else { + qemu_irq_lower(s->pic_irq[even >> 1]); + } + + return; +} + +static void exynos4210_irq_gate_reset(DeviceState *d) +{ + Exynos4210IRQGateState *s = (Exynos4210IRQGateState *)d; + + memset(&s->gpio_level, 0, sizeof(s->gpio_level)); +} + +/* + * IRQ Gate initialization. + */ +static int exynos4210_irq_gate_init(SysBusDevice *dev) +{ + unsigned int i; + Exynos4210IRQGateState *s = + FROM_SYSBUS(Exynos4210IRQGateState, dev); + + /* Allocate general purpose input signals and connect a handler to each of + * them */ + qdev_init_gpio_in(&s->busdev.qdev, exynos4210_irq_gate_handler, + EXYNOS4210_IRQ_GATE_NINPUTS); + + /* Connect SysBusDev irqs to device specific irqs */ + for (i = 0; i < NCPU; i++) { + sysbus_init_irq(dev, &s->pic_irq[i]); + } + + return 0; +} + +static void exynos4210_irq_gate_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = exynos4210_irq_gate_init; + dc->reset = exynos4210_irq_gate_reset; + dc->vmsd = &vmstate_exynos4210_irq_gate; +} + +static TypeInfo exynos4210_irq_gate_info = { + .name = "exynos4210.irq_gate", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Exynos4210IRQGateState), + .class_init = exynos4210_irq_gate_class_init, +}; + +static void exynos4210_irq_gate_register_types(void) +{ + type_register_static(&exynos4210_irq_gate_info); +} + +type_init(exynos4210_irq_gate_register_types) -- cgit v1.1 From 0caa711335a676225041d014d49e65992f9f269f Mon Sep 17 00:00:00 2001 From: Evgeny Voevodin Date: Thu, 16 Feb 2012 09:56:05 +0000 Subject: ARM: Samsung exynos4210-based boards emulation Add initial support of NURI and SMDKC210 boards Signed-off-by: Evgeny Voevodin Signed-off-by: Peter Maydell --- hw/exynos4210.c | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/exynos4210.h | 40 +++++++++++ hw/exynos4_boards.c | 153 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 384 insertions(+) create mode 100644 hw/exynos4210.c create mode 100644 hw/exynos4_boards.c (limited to 'hw') diff --git a/hw/exynos4210.c b/hw/exynos4210.c new file mode 100644 index 0000000..5b1e24c --- /dev/null +++ b/hw/exynos4210.c @@ -0,0 +1,191 @@ +/* + * Samsung exynos4210 SoC emulation + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. All rights reserved. + * Maksim Kozlov + * Evgeny Voevodin + * Igor Mitsyanko + * + * 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 . + * + */ + +#include "boards.h" +#include "sysemu.h" +#include "sysbus.h" +#include "arm-misc.h" +#include "exynos4210.h" + +#define EXYNOS4210_CHIPID_ADDR 0x10000000 + +/* External GIC */ +#define EXYNOS4210_EXT_GIC_CPU_BASE_ADDR 0x10480000 +#define EXYNOS4210_EXT_GIC_DIST_BASE_ADDR 0x10490000 + +/* Combiner */ +#define EXYNOS4210_EXT_COMBINER_BASE_ADDR 0x10440000 +#define EXYNOS4210_INT_COMBINER_BASE_ADDR 0x10448000 + +static uint8_t chipid_and_omr[] = { 0x11, 0x02, 0x21, 0x43, + 0x09, 0x00, 0x00, 0x00 }; + +Exynos4210State *exynos4210_init(MemoryRegion *system_mem, + unsigned long ram_size) +{ + qemu_irq cpu_irq[4]; + int n; + Exynos4210State *s = g_new(Exynos4210State, 1); + qemu_irq *irqp; + qemu_irq gate_irq[EXYNOS4210_IRQ_GATE_NINPUTS]; + unsigned long mem_size; + DeviceState *dev; + SysBusDevice *busdev; + + for (n = 0; n < EXYNOS4210_NCPUS; n++) { + s->env[n] = cpu_init("cortex-a9"); + if (!s->env[n]) { + fprintf(stderr, "Unable to find CPU %d definition\n", n); + exit(1); + } + /* Create PIC controller for each processor instance */ + irqp = arm_pic_init_cpu(s->env[n]); + + /* + * Get GICs gpio_in cpu_irq to connect a combiner to them later. + * Use only IRQ for a while. + */ + cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ]; + } + + /*** IRQs ***/ + + s->irq_table = exynos4210_init_irq(&s->irqs); + + /* IRQ Gate */ + dev = qdev_create(NULL, "exynos4210.irq_gate"); + qdev_init_nofail(dev); + /* Get IRQ Gate input in gate_irq */ + for (n = 0; n < EXYNOS4210_IRQ_GATE_NINPUTS; n++) { + gate_irq[n] = qdev_get_gpio_in(dev, n); + } + busdev = sysbus_from_qdev(dev); + /* Connect IRQ Gate output to cpu_irq */ + for (n = 0; n < EXYNOS4210_NCPUS; n++) { + sysbus_connect_irq(busdev, n, cpu_irq[n]); + } + + /* Private memory region and Internal GIC */ + dev = qdev_create(NULL, "a9mpcore_priv"); + qdev_prop_set_uint32(dev, "num-cpu", EXYNOS4210_NCPUS); + qdev_init_nofail(dev); + busdev = sysbus_from_qdev(dev); + sysbus_mmio_map(busdev, 0, EXYNOS4210_SMP_PRIVATE_BASE_ADDR); + for (n = 0; n < EXYNOS4210_NCPUS; n++) { + sysbus_connect_irq(busdev, n, gate_irq[n * 2]); + } + for (n = 0; n < EXYNOS4210_INT_GIC_NIRQ; n++) { + s->irqs.int_gic_irq[n] = qdev_get_gpio_in(dev, n); + } + + /* Cache controller */ + sysbus_create_simple("l2x0", EXYNOS4210_L2X0_BASE_ADDR, NULL); + + /* External GIC */ + dev = qdev_create(NULL, "exynos4210.gic"); + qdev_prop_set_uint32(dev, "num-cpu", EXYNOS4210_NCPUS); + qdev_init_nofail(dev); + busdev = sysbus_from_qdev(dev); + /* Map CPU interface */ + sysbus_mmio_map(busdev, 0, EXYNOS4210_EXT_GIC_CPU_BASE_ADDR); + /* Map Distributer interface */ + sysbus_mmio_map(busdev, 1, EXYNOS4210_EXT_GIC_DIST_BASE_ADDR); + for (n = 0; n < EXYNOS4210_NCPUS; n++) { + sysbus_connect_irq(busdev, n, gate_irq[n * 2 + 1]); + } + for (n = 0; n < EXYNOS4210_EXT_GIC_NIRQ; n++) { + s->irqs.ext_gic_irq[n] = qdev_get_gpio_in(dev, n); + } + + /* Internal Interrupt Combiner */ + dev = qdev_create(NULL, "exynos4210.combiner"); + qdev_init_nofail(dev); + busdev = sysbus_from_qdev(dev); + for (n = 0; n < EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ; n++) { + sysbus_connect_irq(busdev, n, s->irqs.int_gic_irq[n]); + } + exynos4210_combiner_get_gpioin(&s->irqs, dev, 0); + sysbus_mmio_map(busdev, 0, EXYNOS4210_INT_COMBINER_BASE_ADDR); + + /* External Interrupt Combiner */ + dev = qdev_create(NULL, "exynos4210.combiner"); + qdev_prop_set_uint32(dev, "external", 1); + qdev_init_nofail(dev); + busdev = sysbus_from_qdev(dev); + for (n = 0; n < EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ; n++) { + sysbus_connect_irq(busdev, n, s->irqs.ext_gic_irq[n]); + } + exynos4210_combiner_get_gpioin(&s->irqs, dev, 1); + sysbus_mmio_map(busdev, 0, EXYNOS4210_EXT_COMBINER_BASE_ADDR); + + /* Initialize board IRQs. */ + exynos4210_init_board_irqs(&s->irqs); + + /*** Memory ***/ + + /* Chip-ID and OMR */ + memory_region_init_ram_ptr(&s->chipid_mem, "exynos4210.chipid", + sizeof(chipid_and_omr), chipid_and_omr); + memory_region_set_readonly(&s->chipid_mem, true); + memory_region_add_subregion(system_mem, EXYNOS4210_CHIPID_ADDR, + &s->chipid_mem); + + /* Internal ROM */ + memory_region_init_ram(&s->irom_mem, "exynos4210.irom", + EXYNOS4210_IROM_SIZE); + memory_region_set_readonly(&s->irom_mem, true); + memory_region_add_subregion(system_mem, EXYNOS4210_IROM_BASE_ADDR, + &s->irom_mem); + /* mirror of iROM */ + memory_region_init_alias(&s->irom_alias_mem, "exynos4210.irom_alias", + &s->irom_mem, + EXYNOS4210_IROM_BASE_ADDR, + EXYNOS4210_IROM_SIZE); + memory_region_set_readonly(&s->irom_alias_mem, true); + memory_region_add_subregion(system_mem, EXYNOS4210_IROM_MIRROR_BASE_ADDR, + &s->irom_alias_mem); + + /* Internal RAM */ + memory_region_init_ram(&s->iram_mem, "exynos4210.iram", + EXYNOS4210_IRAM_SIZE); + vmstate_register_ram_global(&s->iram_mem); + memory_region_add_subregion(system_mem, EXYNOS4210_IRAM_BASE_ADDR, + &s->iram_mem); + + /* DRAM */ + mem_size = ram_size; + if (mem_size > EXYNOS4210_DRAM_MAX_SIZE) { + memory_region_init_ram(&s->dram1_mem, "exynos4210.dram1", + mem_size - EXYNOS4210_DRAM_MAX_SIZE); + vmstate_register_ram_global(&s->dram1_mem); + memory_region_add_subregion(system_mem, EXYNOS4210_DRAM1_BASE_ADDR, + &s->dram1_mem); + mem_size = EXYNOS4210_DRAM_MAX_SIZE; + } + memory_region_init_ram(&s->dram0_mem, "exynos4210.dram0", mem_size); + vmstate_register_ram_global(&s->dram0_mem); + memory_region_add_subregion(system_mem, EXYNOS4210_DRAM0_BASE_ADDR, + &s->dram0_mem); + + return s; +} diff --git a/hw/exynos4210.h b/hw/exynos4210.h index ef4732f..0026a52 100644 --- a/hw/exynos4210.h +++ b/hw/exynos4210.h @@ -31,6 +31,28 @@ #define EXYNOS4210_NCPUS 2 +#define EXYNOS4210_DRAM0_BASE_ADDR 0x40000000 +#define EXYNOS4210_DRAM1_BASE_ADDR 0xa0000000 +#define EXYNOS4210_DRAM_MAX_SIZE 0x60000000 /* 1.5 GB */ + +#define EXYNOS4210_IROM_BASE_ADDR 0x00000000 +#define EXYNOS4210_IROM_SIZE 0x00010000 /* 64 KB */ +#define EXYNOS4210_IROM_MIRROR_BASE_ADDR 0x02000000 +#define EXYNOS4210_IROM_MIRROR_SIZE 0x00010000 /* 64 KB */ + +#define EXYNOS4210_IRAM_BASE_ADDR 0x02020000 +#define EXYNOS4210_IRAM_SIZE 0x00020000 /* 128 KB */ + +/* Secondary CPU startup code is in IROM memory */ +#define EXYNOS4210_SMP_BOOT_ADDR EXYNOS4210_IROM_BASE_ADDR +#define EXYNOS4210_SMP_BOOT_SIZE 0x1000 +#define EXYNOS4210_BASE_BOOT_ADDR EXYNOS4210_DRAM0_BASE_ADDR +/* Secondary CPU polling address to get loader start from */ +#define EXYNOS4210_SECOND_CPU_BOOTREG 0x10020814 + +#define EXYNOS4210_SMP_PRIVATE_BASE_ADDR 0x10500000 +#define EXYNOS4210_L2X0_BASE_ADDR 0x10502000 + /* * exynos4210 IRQ subsystem stub definitions. */ @@ -60,6 +82,24 @@ typedef struct Exynos4210Irq { qemu_irq board_irqs[EXYNOS4210_MAX_INT_COMBINER_IN_IRQ]; } Exynos4210Irq; +typedef struct Exynos4210State { + CPUState * env[EXYNOS4210_NCPUS]; + Exynos4210Irq irqs; + qemu_irq *irq_table; + + MemoryRegion chipid_mem; + MemoryRegion iram_mem; + MemoryRegion irom_mem; + MemoryRegion irom_alias_mem; + MemoryRegion dram0_mem; + MemoryRegion dram1_mem; + MemoryRegion boot_secondary; + MemoryRegion bootreg_mem; +} Exynos4210State; + +Exynos4210State *exynos4210_init(MemoryRegion *system_mem, + unsigned long ram_size); + /* Initialize exynos4210 IRQ subsystem stub */ qemu_irq *exynos4210_init_irq(Exynos4210Irq *env); diff --git a/hw/exynos4_boards.c b/hw/exynos4_boards.c new file mode 100644 index 0000000..767dc45 --- /dev/null +++ b/hw/exynos4_boards.c @@ -0,0 +1,153 @@ +/* + * Samsung exynos4 SoC based boards emulation + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. All rights reserved. + * Maksim Kozlov + * Evgeny Voevodin + * Igor Mitsyanko + * + * 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 . + * + */ + +#include "sysemu.h" +#include "sysbus.h" +#include "arm-misc.h" +#include "exec-memory.h" +#include "exynos4210.h" +#include "boards.h" + +#undef DEBUG + +//#define DEBUG + +#ifdef DEBUG + #undef PRINT_DEBUG + #define PRINT_DEBUG(fmt, args...) \ + do { \ + fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ + } while (0) +#else + #define PRINT_DEBUG(fmt, args...) do {} while (0) +#endif + +typedef enum Exynos4BoardType { + EXYNOS4_BOARD_NURI, + EXYNOS4_BOARD_SMDKC210, + EXYNOS4_NUM_OF_BOARDS +} Exynos4BoardType; + +static int exynos4_board_id[EXYNOS4_NUM_OF_BOARDS] = { + [EXYNOS4_BOARD_NURI] = 0xD33, + [EXYNOS4_BOARD_SMDKC210] = 0xB16, +}; + +static int exynos4_board_smp_bootreg_addr[EXYNOS4_NUM_OF_BOARDS] = { + [EXYNOS4_BOARD_NURI] = EXYNOS4210_SECOND_CPU_BOOTREG, + [EXYNOS4_BOARD_SMDKC210] = EXYNOS4210_SECOND_CPU_BOOTREG, +}; + +static unsigned long exynos4_board_ram_size[EXYNOS4_NUM_OF_BOARDS] = { + [EXYNOS4_BOARD_NURI] = 0x40000000, + [EXYNOS4_BOARD_SMDKC210] = 0x40000000, +}; + +static struct arm_boot_info exynos4_board_binfo = { + .loader_start = EXYNOS4210_BASE_BOOT_ADDR, + .smp_loader_start = EXYNOS4210_SMP_BOOT_ADDR, + .nb_cpus = EXYNOS4210_NCPUS, +}; + +static QEMUMachine exynos4_machines[EXYNOS4_NUM_OF_BOARDS]; + +static Exynos4210State *exynos4_boards_init_common( + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + Exynos4BoardType board_type) +{ + if (smp_cpus != EXYNOS4210_NCPUS) { + fprintf(stderr, "%s board supports only %d CPU cores. Ignoring smp_cpus" + " value.\n", + exynos4_machines[board_type].name, + exynos4_machines[board_type].max_cpus); + } + + exynos4_board_binfo.ram_size = exynos4_board_ram_size[board_type]; + exynos4_board_binfo.board_id = exynos4_board_id[board_type]; + exynos4_board_binfo.smp_bootreg_addr = + exynos4_board_smp_bootreg_addr[board_type]; + exynos4_board_binfo.kernel_filename = kernel_filename; + exynos4_board_binfo.initrd_filename = initrd_filename; + exynos4_board_binfo.kernel_cmdline = kernel_cmdline; + exynos4_board_binfo.smp_priv_base = EXYNOS4210_SMP_PRIVATE_BASE_ADDR; + + PRINT_DEBUG("\n ram_size: %luMiB [0x%08lx]\n" + " kernel_filename: %s\n" + " kernel_cmdline: %s\n" + " initrd_filename: %s\n", + exynos4_board_ram_size[board_type] / 1048576, + exynos4_board_ram_size[board_type], + kernel_filename, + kernel_cmdline, + initrd_filename); + + return exynos4210_init(get_system_memory(), + exynos4_board_ram_size[board_type]); +} + +static void nuri_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + exynos4_boards_init_common(kernel_filename, kernel_cmdline, + initrd_filename, EXYNOS4_BOARD_NURI); + + arm_load_kernel(first_cpu, &exynos4_board_binfo); +} + +static void smdkc210_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + exynos4_boards_init_common(kernel_filename, kernel_cmdline, + initrd_filename, EXYNOS4_BOARD_SMDKC210); + + arm_load_kernel(first_cpu, &exynos4_board_binfo); +} + +static QEMUMachine exynos4_machines[EXYNOS4_NUM_OF_BOARDS] = { + [EXYNOS4_BOARD_NURI] = { + .name = "nuri", + .desc = "Samsung NURI board (Exynos4210)", + .init = nuri_init, + .max_cpus = EXYNOS4210_NCPUS, + }, + [EXYNOS4_BOARD_SMDKC210] = { + .name = "smdkc210", + .desc = "Samsung SMDKC210 board (Exynos4210)", + .init = smdkc210_init, + .max_cpus = EXYNOS4210_NCPUS, + }, +}; + +static void exynos4_machine_init(void) +{ + qemu_register_machine(&exynos4_machines[EXYNOS4_BOARD_NURI]); + qemu_register_machine(&exynos4_machines[EXYNOS4_BOARD_SMDKC210]); +} + +machine_init(exynos4_machine_init); -- cgit v1.1 From e5a4914efc75c8880d7088dc0871766e5b73673f Mon Sep 17 00:00:00 2001 From: Maksim Kozlov Date: Thu, 16 Feb 2012 09:56:05 +0000 Subject: ARM: exynos4210: UART support Add basic support of exynos4210 UART Signed-off-by: Maksim Kozlov Signed-off-by: Evgeny Voevodin Signed-off-by: Peter Maydell --- hw/exynos4210.c | 29 +++ hw/exynos4210.h | 9 + hw/exynos4210_uart.c | 676 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 714 insertions(+) create mode 100644 hw/exynos4210_uart.c (limited to 'hw') diff --git a/hw/exynos4210.c b/hw/exynos4210.c index 5b1e24c..555adc0 100644 --- a/hw/exynos4210.c +++ b/hw/exynos4210.c @@ -29,6 +29,18 @@ #define EXYNOS4210_CHIPID_ADDR 0x10000000 +/* UART's definitions */ +#define EXYNOS4210_UART0_BASE_ADDR 0x13800000 +#define EXYNOS4210_UART1_BASE_ADDR 0x13810000 +#define EXYNOS4210_UART2_BASE_ADDR 0x13820000 +#define EXYNOS4210_UART3_BASE_ADDR 0x13830000 +#define EXYNOS4210_UART0_FIFO_SIZE 256 +#define EXYNOS4210_UART1_FIFO_SIZE 64 +#define EXYNOS4210_UART2_FIFO_SIZE 16 +#define EXYNOS4210_UART3_FIFO_SIZE 16 +/* Interrupt Group of External Interrupt Combiner for UART */ +#define EXYNOS4210_UART_INT_GRP 26 + /* External GIC */ #define EXYNOS4210_EXT_GIC_CPU_BASE_ADDR 0x10480000 #define EXYNOS4210_EXT_GIC_DIST_BASE_ADDR 0x10490000 @@ -187,5 +199,22 @@ Exynos4210State *exynos4210_init(MemoryRegion *system_mem, memory_region_add_subregion(system_mem, EXYNOS4210_DRAM0_BASE_ADDR, &s->dram0_mem); + /*** UARTs ***/ + exynos4210_uart_create(EXYNOS4210_UART0_BASE_ADDR, + EXYNOS4210_UART0_FIFO_SIZE, 0, NULL, + s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 0)]); + + exynos4210_uart_create(EXYNOS4210_UART1_BASE_ADDR, + EXYNOS4210_UART1_FIFO_SIZE, 1, NULL, + s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 1)]); + + exynos4210_uart_create(EXYNOS4210_UART2_BASE_ADDR, + EXYNOS4210_UART2_FIFO_SIZE, 2, NULL, + s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 2)]); + + exynos4210_uart_create(EXYNOS4210_UART3_BASE_ADDR, + EXYNOS4210_UART3_FIFO_SIZE, 3, NULL, + s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 3)]); + return s; } diff --git a/hw/exynos4210.h b/hw/exynos4210.h index 0026a52..e7522f8 100644 --- a/hw/exynos4210.h +++ b/hw/exynos4210.h @@ -119,4 +119,13 @@ uint32_t exynos4210_get_irq(uint32_t grp, uint32_t bit); void exynos4210_combiner_get_gpioin(Exynos4210Irq *irqs, DeviceState *dev, int ext); +/* + * exynos4210 UART + */ +DeviceState *exynos4210_uart_create(target_phys_addr_t addr, + int fifo_size, + int channel, + CharDriverState *chr, + qemu_irq irq); + #endif /* EXYNOS4210_H_ */ diff --git a/hw/exynos4210_uart.c b/hw/exynos4210_uart.c new file mode 100644 index 0000000..73a9c18 --- /dev/null +++ b/hw/exynos4210_uart.c @@ -0,0 +1,676 @@ +/* + * Exynos4210 UART Emulation + * + * Copyright (C) 2011 Samsung Electronics Co Ltd. + * Maksim Kozlov, + * + * 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 . + * + */ + +#include "sysbus.h" +#include "sysemu.h" +#include "qemu-char.h" + +#include "exynos4210.h" + +#undef DEBUG_UART +#undef DEBUG_UART_EXTEND +#undef DEBUG_IRQ +#undef DEBUG_Rx_DATA +#undef DEBUG_Tx_DATA + +#define DEBUG_UART 0 +#define DEBUG_UART_EXTEND 0 +#define DEBUG_IRQ 0 +#define DEBUG_Rx_DATA 0 +#define DEBUG_Tx_DATA 0 + +#if DEBUG_UART +#define PRINT_DEBUG(fmt, args...) \ + do { \ + fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ + } while (0) + +#if DEBUG_UART_EXTEND +#define PRINT_DEBUG_EXTEND(fmt, args...) \ + do { \ + fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ + } while (0) +#else +#define PRINT_DEBUG_EXTEND(fmt, args...) \ + do {} while (0) +#endif /* EXTEND */ + +#else +#define PRINT_DEBUG(fmt, args...) \ + do {} while (0) +#define PRINT_DEBUG_EXTEND(fmt, args...) \ + do {} while (0) +#endif + +#define PRINT_ERROR(fmt, args...) \ + do { \ + fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ + } while (0) + +/* + * Offsets for UART registers relative to SFR base address + * for UARTn + * + */ +#define ULCON 0x0000 /* Line Control */ +#define UCON 0x0004 /* Control */ +#define UFCON 0x0008 /* FIFO Control */ +#define UMCON 0x000C /* Modem Control */ +#define UTRSTAT 0x0010 /* Tx/Rx Status */ +#define UERSTAT 0x0014 /* UART Error Status */ +#define UFSTAT 0x0018 /* FIFO Status */ +#define UMSTAT 0x001C /* Modem Status */ +#define UTXH 0x0020 /* Transmit Buffer */ +#define URXH 0x0024 /* Receive Buffer */ +#define UBRDIV 0x0028 /* Baud Rate Divisor */ +#define UFRACVAL 0x002C /* Divisor Fractional Value */ +#define UINTP 0x0030 /* Interrupt Pending */ +#define UINTSP 0x0034 /* Interrupt Source Pending */ +#define UINTM 0x0038 /* Interrupt Mask */ + +/* + * for indexing register in the uint32_t array + * + * 'reg' - register offset (see offsets definitions above) + * + */ +#define I_(reg) (reg / sizeof(uint32_t)) + +typedef struct Exynos4210UartReg { + const char *name; /* the only reason is the debug output */ + target_phys_addr_t offset; + uint32_t reset_value; +} Exynos4210UartReg; + +static Exynos4210UartReg exynos4210_uart_regs[] = { + {"ULCON", ULCON, 0x00000000}, + {"UCON", UCON, 0x00003000}, + {"UFCON", UFCON, 0x00000000}, + {"UMCON", UMCON, 0x00000000}, + {"UTRSTAT", UTRSTAT, 0x00000006}, /* RO */ + {"UERSTAT", UERSTAT, 0x00000000}, /* RO */ + {"UFSTAT", UFSTAT, 0x00000000}, /* RO */ + {"UMSTAT", UMSTAT, 0x00000000}, /* RO */ + {"UTXH", UTXH, 0x5c5c5c5c}, /* WO, undefined reset value*/ + {"URXH", URXH, 0x00000000}, /* RO */ + {"UBRDIV", UBRDIV, 0x00000000}, + {"UFRACVAL", UFRACVAL, 0x00000000}, + {"UINTP", UINTP, 0x00000000}, + {"UINTSP", UINTSP, 0x00000000}, + {"UINTM", UINTM, 0x00000000}, +}; + +#define EXYNOS4210_UART_REGS_MEM_SIZE 0x3C + +/* UART FIFO Control */ +#define UFCON_FIFO_ENABLE 0x1 +#define UFCON_Rx_FIFO_RESET 0x2 +#define UFCON_Tx_FIFO_RESET 0x4 +#define UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT 8 +#define UFCON_Tx_FIFO_TRIGGER_LEVEL (7 << UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT) +#define UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT 4 +#define UFCON_Rx_FIFO_TRIGGER_LEVEL (7 << UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT) + +/* Uart FIFO Status */ +#define UFSTAT_Rx_FIFO_COUNT 0xff +#define UFSTAT_Rx_FIFO_FULL 0x100 +#define UFSTAT_Rx_FIFO_ERROR 0x200 +#define UFSTAT_Tx_FIFO_COUNT_SHIFT 16 +#define UFSTAT_Tx_FIFO_COUNT (0xff << UFSTAT_Tx_FIFO_COUNT_SHIFT) +#define UFSTAT_Tx_FIFO_FULL_SHIFT 24 +#define UFSTAT_Tx_FIFO_FULL (1 << UFSTAT_Tx_FIFO_FULL_SHIFT) + +/* UART Interrupt Source Pending */ +#define UINTSP_RXD 0x1 /* Receive interrupt */ +#define UINTSP_ERROR 0x2 /* Error interrupt */ +#define UINTSP_TXD 0x4 /* Transmit interrupt */ +#define UINTSP_MODEM 0x8 /* Modem interrupt */ + +/* UART Line Control */ +#define ULCON_IR_MODE_SHIFT 6 +#define ULCON_PARITY_SHIFT 3 +#define ULCON_STOP_BIT_SHIFT 1 + +/* UART Tx/Rx Status */ +#define UTRSTAT_TRANSMITTER_EMPTY 0x4 +#define UTRSTAT_Tx_BUFFER_EMPTY 0x2 +#define UTRSTAT_Rx_BUFFER_DATA_READY 0x1 + +/* UART Error Status */ +#define UERSTAT_OVERRUN 0x1 +#define UERSTAT_PARITY 0x2 +#define UERSTAT_FRAME 0x4 +#define UERSTAT_BREAK 0x8 + +typedef struct { + uint8_t *data; + uint32_t sp, rp; /* store and retrieve pointers */ + uint32_t size; +} Exynos4210UartFIFO; + +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + + uint32_t reg[EXYNOS4210_UART_REGS_MEM_SIZE / sizeof(uint32_t)]; + Exynos4210UartFIFO rx; + Exynos4210UartFIFO tx; + + CharDriverState *chr; + qemu_irq irq; + + uint32_t channel; + +} Exynos4210UartState; + + +#if DEBUG_UART +/* Used only for debugging inside PRINT_DEBUG_... macros */ +static const char *exynos4210_uart_regname(target_phys_addr_t offset) +{ + + int regs_number = sizeof(exynos4210_uart_regs) / sizeof(Exynos4210UartReg); + int i; + + for (i = 0; i < regs_number; i++) { + if (offset == exynos4210_uart_regs[i].offset) { + return exynos4210_uart_regs[i].name; + } + } + + return NULL; +} +#endif + + +static void fifo_store(Exynos4210UartFIFO *q, uint8_t ch) +{ + q->data[q->sp] = ch; + q->sp = (q->sp + 1) % q->size; +} + +static uint8_t fifo_retrieve(Exynos4210UartFIFO *q) +{ + uint8_t ret = q->data[q->rp]; + q->rp = (q->rp + 1) % q->size; + return ret; +} + +static int fifo_elements_number(Exynos4210UartFIFO *q) +{ + if (q->sp < q->rp) { + return q->size - q->rp + q->sp; + } + + return q->sp - q->rp; +} + +static int fifo_empty_elements_number(Exynos4210UartFIFO *q) +{ + return q->size - fifo_elements_number(q); +} + +static void fifo_reset(Exynos4210UartFIFO *q) +{ + if (q->data != NULL) { + g_free(q->data); + q->data = NULL; + } + + q->data = (uint8_t *)g_malloc0(q->size); + + q->sp = 0; + q->rp = 0; +} + +static uint32_t exynos4210_uart_Tx_FIFO_trigger_level(Exynos4210UartState *s) +{ + uint32_t level = 0; + uint32_t reg; + + reg = (s->reg[I_(UFCON)] && UFCON_Tx_FIFO_TRIGGER_LEVEL) >> + UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT; + + switch (s->channel) { + case 0: + level = reg * 32; + break; + case 1: + case 4: + level = reg * 8; + break; + case 2: + case 3: + level = reg * 2; + break; + default: + level = 0; + PRINT_ERROR("Wrong UART channel number: %d\n", s->channel); + } + + return level; +} + +static void exynos4210_uart_update_irq(Exynos4210UartState *s) +{ + /* + * The Tx interrupt is always requested if the number of data in the + * transmit FIFO is smaller than the trigger level. + */ + if (s->reg[I_(UFCON)] && UFCON_FIFO_ENABLE) { + + uint32_t count = (s->reg[I_(UFSTAT)] && UFSTAT_Tx_FIFO_COUNT) >> + UFSTAT_Tx_FIFO_COUNT_SHIFT; + + if (count <= exynos4210_uart_Tx_FIFO_trigger_level(s)) { + s->reg[I_(UINTSP)] |= UINTSP_TXD; + } + } + + s->reg[I_(UINTP)] = s->reg[I_(UINTSP)] & ~s->reg[I_(UINTM)]; + + if (s->reg[I_(UINTP)]) { + qemu_irq_raise(s->irq); + +#if DEBUG_IRQ + fprintf(stderr, "UART%d: IRQ has been raised: %08x\n", + s->channel, s->reg[I_(UINTP)]); +#endif + + } else { + qemu_irq_lower(s->irq); + } +} + +static void exynos4210_uart_update_parameters(Exynos4210UartState *s) +{ + int speed, parity, data_bits, stop_bits, frame_size; + QEMUSerialSetParams ssp; + uint64_t uclk_rate; + + if (s->reg[I_(UBRDIV)] == 0) { + return; + } + + frame_size = 1; /* start bit */ + if (s->reg[I_(ULCON)] & 0x20) { + frame_size++; /* parity bit */ + if (s->reg[I_(ULCON)] & 0x28) { + parity = 'E'; + } else { + parity = 'O'; + } + } else { + parity = 'N'; + } + + if (s->reg[I_(ULCON)] & 0x4) { + stop_bits = 2; + } else { + stop_bits = 1; + } + + data_bits = (s->reg[I_(ULCON)] & 0x3) + 5; + + frame_size += data_bits + stop_bits; + + uclk_rate = 24000000; + + speed = uclk_rate / ((16 * (s->reg[I_(UBRDIV)]) & 0xffff) + + (s->reg[I_(UFRACVAL)] & 0x7) + 16); + + ssp.speed = speed; + ssp.parity = parity; + ssp.data_bits = data_bits; + ssp.stop_bits = stop_bits; + + qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp); + + PRINT_DEBUG("UART%d: speed: %d, parity: %c, data: %d, stop: %d\n", + s->channel, speed, parity, data_bits, stop_bits); +} + +static void exynos4210_uart_write(void *opaque, target_phys_addr_t offset, + uint64_t val, unsigned size) +{ + Exynos4210UartState *s = (Exynos4210UartState *)opaque; + uint8_t ch; + + PRINT_DEBUG_EXTEND("UART%d: <0x%04x> %s <- 0x%08llx\n", s->channel, + offset, exynos4210_uart_regname(offset), (long long unsigned int)val); + + switch (offset) { + case ULCON: + case UBRDIV: + case UFRACVAL: + s->reg[I_(offset)] = val; + exynos4210_uart_update_parameters(s); + break; + case UFCON: + s->reg[I_(UFCON)] = val; + if (val & UFCON_Rx_FIFO_RESET) { + fifo_reset(&s->rx); + s->reg[I_(UFCON)] &= ~UFCON_Rx_FIFO_RESET; + PRINT_DEBUG("UART%d: Rx FIFO Reset\n", s->channel); + } + if (val & UFCON_Tx_FIFO_RESET) { + fifo_reset(&s->tx); + s->reg[I_(UFCON)] &= ~UFCON_Tx_FIFO_RESET; + PRINT_DEBUG("UART%d: Tx FIFO Reset\n", s->channel); + } + break; + + case UTXH: + if (s->chr) { + s->reg[I_(UTRSTAT)] &= ~(UTRSTAT_TRANSMITTER_EMPTY | + UTRSTAT_Tx_BUFFER_EMPTY); + ch = (uint8_t)val; + qemu_chr_fe_write(s->chr, &ch, 1); +#if DEBUG_Tx_DATA + fprintf(stderr, "%c", ch); +#endif + s->reg[I_(UTRSTAT)] |= UTRSTAT_TRANSMITTER_EMPTY | + UTRSTAT_Tx_BUFFER_EMPTY; + s->reg[I_(UINTSP)] |= UINTSP_TXD; + exynos4210_uart_update_irq(s); + } + break; + + case UINTP: + s->reg[I_(UINTP)] &= ~val; + s->reg[I_(UINTSP)] &= ~val; + PRINT_DEBUG("UART%d: UINTP [%04x] have been cleared: %08x\n", + s->channel, offset, s->reg[I_(UINTP)]); + exynos4210_uart_update_irq(s); + break; + case UTRSTAT: + case UERSTAT: + case UFSTAT: + case UMSTAT: + case URXH: + PRINT_DEBUG("UART%d: Trying to write into RO register: %s [%04x]\n", + s->channel, exynos4210_uart_regname(offset), offset); + break; + case UINTSP: + s->reg[I_(UINTSP)] &= ~val; + break; + case UINTM: + s->reg[I_(UINTM)] = val; + exynos4210_uart_update_irq(s); + break; + case UCON: + case UMCON: + default: + s->reg[I_(offset)] = val; + break; + } +} +static uint64_t exynos4210_uart_read(void *opaque, target_phys_addr_t offset, + unsigned size) +{ + Exynos4210UartState *s = (Exynos4210UartState *)opaque; + uint32_t res; + + switch (offset) { + case UERSTAT: /* Read Only */ + res = s->reg[I_(UERSTAT)]; + s->reg[I_(UERSTAT)] = 0; + return res; + case UFSTAT: /* Read Only */ + s->reg[I_(UFSTAT)] = fifo_elements_number(&s->rx) & 0xff; + if (fifo_empty_elements_number(&s->rx) == 0) { + s->reg[I_(UFSTAT)] |= UFSTAT_Rx_FIFO_FULL; + s->reg[I_(UFSTAT)] &= ~0xff; + } + return s->reg[I_(UFSTAT)]; + case URXH: + if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) { + if (fifo_elements_number(&s->rx)) { + res = fifo_retrieve(&s->rx); +#if DEBUG_Rx_DATA + fprintf(stderr, "%c", res); +#endif + if (!fifo_elements_number(&s->rx)) { + s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_BUFFER_DATA_READY; + } else { + s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY; + } + } else { + s->reg[I_(UINTSP)] |= UINTSP_ERROR; + exynos4210_uart_update_irq(s); + res = 0; + } + } else { + s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_BUFFER_DATA_READY; + res = s->reg[I_(URXH)]; + } + return res; + case UTXH: + PRINT_DEBUG("UART%d: Trying to read from WO register: %s [%04x]\n", + s->channel, exynos4210_uart_regname(offset), offset); + break; + default: + return s->reg[I_(offset)]; + } + + return 0; +} + +static const MemoryRegionOps exynos4210_uart_ops = { + .read = exynos4210_uart_read, + .write = exynos4210_uart_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .max_access_size = 4, + .unaligned = false + }, +}; + +static int exynos4210_uart_can_receive(void *opaque) +{ + Exynos4210UartState *s = (Exynos4210UartState *)opaque; + + return fifo_empty_elements_number(&s->rx); +} + + +static void exynos4210_uart_receive(void *opaque, const uint8_t *buf, int size) +{ + Exynos4210UartState *s = (Exynos4210UartState *)opaque; + int i; + + if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) { + if (fifo_empty_elements_number(&s->rx) < size) { + for (i = 0; i < fifo_empty_elements_number(&s->rx); i++) { + fifo_store(&s->rx, buf[i]); + } + s->reg[I_(UINTSP)] |= UINTSP_ERROR; + s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY; + } else { + for (i = 0; i < size; i++) { + fifo_store(&s->rx, buf[i]); + } + s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY; + } + /* XXX: Around here we maybe should check Rx trigger level */ + s->reg[I_(UINTSP)] |= UINTSP_RXD; + } else { + s->reg[I_(URXH)] = buf[0]; + s->reg[I_(UINTSP)] |= UINTSP_RXD; + s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY; + } + + exynos4210_uart_update_irq(s); +} + + +static void exynos4210_uart_event(void *opaque, int event) +{ + Exynos4210UartState *s = (Exynos4210UartState *)opaque; + + if (event == CHR_EVENT_BREAK) { + /* When the RxDn is held in logic 0, then a null byte is pushed into the + * fifo */ + fifo_store(&s->rx, '\0'); + s->reg[I_(UERSTAT)] |= UERSTAT_BREAK; + exynos4210_uart_update_irq(s); + } +} + + +static void exynos4210_uart_reset(DeviceState *dev) +{ + Exynos4210UartState *s = + container_of(dev, Exynos4210UartState, busdev.qdev); + int regs_number = sizeof(exynos4210_uart_regs)/sizeof(Exynos4210UartReg); + int i; + + for (i = 0; i < regs_number; i++) { + s->reg[I_(exynos4210_uart_regs[i].offset)] = + exynos4210_uart_regs[i].reset_value; + } + + fifo_reset(&s->rx); + fifo_reset(&s->tx); + + PRINT_DEBUG("UART%d: Rx FIFO size: %d\n", s->channel, s->rx.size); +} + +static const VMStateDescription vmstate_exynos4210_uart_fifo = { + .name = "exynos4210.uart.fifo", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(sp, Exynos4210UartFIFO), + VMSTATE_UINT32(rp, Exynos4210UartFIFO), + VMSTATE_VBUFFER_UINT32(data, Exynos4210UartFIFO, 1, NULL, 0, size), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_exynos4210_uart = { + .name = "exynos4210.uart", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(rx, Exynos4210UartState, 1, + vmstate_exynos4210_uart_fifo, Exynos4210UartFIFO), + VMSTATE_UINT32_ARRAY(reg, Exynos4210UartState, + EXYNOS4210_UART_REGS_MEM_SIZE / sizeof(uint32_t)), + VMSTATE_END_OF_LIST() + } +}; + +DeviceState *exynos4210_uart_create(target_phys_addr_t addr, + int fifo_size, + int channel, + CharDriverState *chr, + qemu_irq irq) +{ + DeviceState *dev; + SysBusDevice *bus; + + const char chr_name[] = "serial"; + char label[ARRAY_SIZE(chr_name) + 1]; + + dev = qdev_create(NULL, "exynos4210.uart"); + + if (!chr) { + if (channel >= MAX_SERIAL_PORTS) { + hw_error("Only %d serial ports are supported by QEMU.\n", + MAX_SERIAL_PORTS); + } + chr = serial_hds[channel]; + if (!chr) { + snprintf(label, ARRAY_SIZE(label), "%s%d", chr_name, channel); + chr = qemu_chr_new(label, "null", NULL); + if (!(chr)) { + hw_error("Can't assign serial port to UART%d.\n", channel); + } + } + } + + qdev_prop_set_chr(dev, "chardev", chr); + qdev_prop_set_uint32(dev, "channel", channel); + qdev_prop_set_uint32(dev, "rx-size", fifo_size); + qdev_prop_set_uint32(dev, "tx-size", fifo_size); + + bus = sysbus_from_qdev(dev); + qdev_init_nofail(dev); + if (addr != (target_phys_addr_t)-1) { + sysbus_mmio_map(bus, 0, addr); + } + sysbus_connect_irq(bus, 0, irq); + + return dev; +} + +static int exynos4210_uart_init(SysBusDevice *dev) +{ + Exynos4210UartState *s = FROM_SYSBUS(Exynos4210UartState, dev); + + /* memory mapping */ + memory_region_init_io(&s->iomem, &exynos4210_uart_ops, s, "exynos4210.uart", + EXYNOS4210_UART_REGS_MEM_SIZE); + sysbus_init_mmio(dev, &s->iomem); + + sysbus_init_irq(dev, &s->irq); + + qemu_chr_add_handlers(s->chr, exynos4210_uart_can_receive, + exynos4210_uart_receive, exynos4210_uart_event, s); + + return 0; +} + +static Property exynos4210_uart_properties[] = { + DEFINE_PROP_CHR("chardev", Exynos4210UartState, chr), + DEFINE_PROP_UINT32("channel", Exynos4210UartState, channel, 0), + DEFINE_PROP_UINT32("rx-size", Exynos4210UartState, rx.size, 16), + DEFINE_PROP_UINT32("tx-size", Exynos4210UartState, tx.size, 16), + DEFINE_PROP_END_OF_LIST(), +}; + +static void exynos4210_uart_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = exynos4210_uart_init; + dc->reset = exynos4210_uart_reset; + dc->props = exynos4210_uart_properties; + dc->vmsd = &vmstate_exynos4210_uart; +} + +static TypeInfo exynos4210_uart_info = { + .name = "exynos4210.uart", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Exynos4210UartState), + .class_init = exynos4210_uart_class_init, +}; + +static void exynos4210_uart_register(void) +{ + type_register_static(&exynos4210_uart_info); +} + +type_init(exynos4210_uart_register) -- cgit v1.1 From 62db8bf39b8a0e3edb631a3d0e5bde840c780fcf Mon Sep 17 00:00:00 2001 From: Evgeny Voevodin Date: Thu, 16 Feb 2012 09:56:05 +0000 Subject: ARM: exynos4210: PWM support. Signed-off-by: Evgeny Voevodin Signed-off-by: Peter Maydell --- hw/exynos4210.c | 12 ++ hw/exynos4210_pwm.c | 422 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 434 insertions(+) create mode 100644 hw/exynos4210_pwm.c (limited to 'hw') diff --git a/hw/exynos4210.c b/hw/exynos4210.c index 555adc0..8ebe5df 100644 --- a/hw/exynos4210.c +++ b/hw/exynos4210.c @@ -29,6 +29,9 @@ #define EXYNOS4210_CHIPID_ADDR 0x10000000 +/* PWM */ +#define EXYNOS4210_PWM_BASE_ADDR 0x139D0000 + /* UART's definitions */ #define EXYNOS4210_UART0_BASE_ADDR 0x13800000 #define EXYNOS4210_UART1_BASE_ADDR 0x13810000 @@ -199,6 +202,15 @@ Exynos4210State *exynos4210_init(MemoryRegion *system_mem, memory_region_add_subregion(system_mem, EXYNOS4210_DRAM0_BASE_ADDR, &s->dram0_mem); + /* PWM */ + sysbus_create_varargs("exynos4210.pwm", EXYNOS4210_PWM_BASE_ADDR, + s->irq_table[exynos4210_get_irq(22, 0)], + s->irq_table[exynos4210_get_irq(22, 1)], + s->irq_table[exynos4210_get_irq(22, 2)], + s->irq_table[exynos4210_get_irq(22, 3)], + s->irq_table[exynos4210_get_irq(22, 4)], + NULL); + /*** UARTs ***/ exynos4210_uart_create(EXYNOS4210_UART0_BASE_ADDR, EXYNOS4210_UART0_FIFO_SIZE, 0, NULL, diff --git a/hw/exynos4210_pwm.c b/hw/exynos4210_pwm.c new file mode 100644 index 0000000..6243e59 --- /dev/null +++ b/hw/exynos4210_pwm.c @@ -0,0 +1,422 @@ +/* + * Samsung exynos4210 Pulse Width Modulation Timer + * + * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. + * All rights reserved. + * + * Evgeny Voevodin + * + * 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 . + */ + +#include "sysbus.h" +#include "qemu-timer.h" +#include "qemu-common.h" +#include "ptimer.h" + +#include "exynos4210.h" + +//#define DEBUG_PWM + +#ifdef DEBUG_PWM +#define DPRINTF(fmt, ...) \ + do { fprintf(stdout, "PWM: [%24s:%5d] " fmt, __func__, __LINE__, \ + ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do {} while (0) +#endif + +#define EXYNOS4210_PWM_TIMERS_NUM 5 +#define EXYNOS4210_PWM_REG_MEM_SIZE 0x50 + +#define TCFG0 0x0000 +#define TCFG1 0x0004 +#define TCON 0x0008 +#define TCNTB0 0x000C +#define TCMPB0 0x0010 +#define TCNTO0 0x0014 +#define TCNTB1 0x0018 +#define TCMPB1 0x001C +#define TCNTO1 0x0020 +#define TCNTB2 0x0024 +#define TCMPB2 0x0028 +#define TCNTO2 0x002C +#define TCNTB3 0x0030 +#define TCMPB3 0x0034 +#define TCNTO3 0x0038 +#define TCNTB4 0x003C +#define TCNTO4 0x0040 +#define TINT_CSTAT 0x0044 + +#define TCNTB(x) (0xC * (x)) +#define TCMPB(x) (0xC * (x) + 1) +#define TCNTO(x) (0xC * (x) + 2) + +#define GET_PRESCALER(reg, x) (((reg) & (0xFF << (8 * (x)))) >> 8 * (x)) +#define GET_DIVIDER(reg, x) (1 << (((reg) & (0xF << (4 * (x)))) >> (4 * (x)))) + +/* + * Attention! Timer4 doesn't have OUTPUT_INVERTER, + * so Auto Reload bit is not accessible by macros! + */ +#define TCON_TIMER_BASE(x) (((x) ? 1 : 0) * 4 + 4 * (x)) +#define TCON_TIMER_START(x) (1 << (TCON_TIMER_BASE(x) + 0)) +#define TCON_TIMER_MANUAL_UPD(x) (1 << (TCON_TIMER_BASE(x) + 1)) +#define TCON_TIMER_OUTPUT_INV(x) (1 << (TCON_TIMER_BASE(x) + 2)) +#define TCON_TIMER_AUTO_RELOAD(x) (1 << (TCON_TIMER_BASE(x) + 3)) +#define TCON_TIMER4_AUTO_RELOAD (1 << 22) + +#define TINT_CSTAT_STATUS(x) (1 << (5 + (x))) +#define TINT_CSTAT_ENABLE(x) (1 << (x)) + +/* timer struct */ +typedef struct { + uint32_t id; /* timer id */ + qemu_irq irq; /* local timer irq */ + uint32_t freq; /* timer frequency */ + + /* use ptimer.c to represent count down timer */ + ptimer_state *ptimer; /* timer */ + + /* registers */ + uint32_t reg_tcntb; /* counter register buffer */ + uint32_t reg_tcmpb; /* compare register buffer */ + + struct Exynos4210PWMState *parent; + +} Exynos4210PWM; + + +typedef struct Exynos4210PWMState { + SysBusDevice busdev; + MemoryRegion iomem; + + uint32_t reg_tcfg[2]; + uint32_t reg_tcon; + uint32_t reg_tint_cstat; + + Exynos4210PWM timer[EXYNOS4210_PWM_TIMERS_NUM]; + +} Exynos4210PWMState; + +/*** VMState ***/ +static const VMStateDescription vmstate_exynos4210_pwm = { + .name = "exynos4210.pwm.pwm", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(id, Exynos4210PWM), + VMSTATE_UINT32(freq, Exynos4210PWM), + VMSTATE_PTIMER(ptimer, Exynos4210PWM), + VMSTATE_UINT32(reg_tcntb, Exynos4210PWM), + VMSTATE_UINT32(reg_tcmpb, Exynos4210PWM), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_exynos4210_pwm_state = { + .name = "exynos4210.pwm", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(reg_tcfg, Exynos4210PWMState, 2), + VMSTATE_UINT32(reg_tcon, Exynos4210PWMState), + VMSTATE_UINT32(reg_tint_cstat, Exynos4210PWMState), + VMSTATE_STRUCT_ARRAY(timer, Exynos4210PWMState, + EXYNOS4210_PWM_TIMERS_NUM, 0, + vmstate_exynos4210_pwm, Exynos4210PWM), + VMSTATE_END_OF_LIST() + } +}; + +/* + * PWM update frequency + */ +static void exynos4210_pwm_update_freq(Exynos4210PWMState *s, uint32_t id) +{ + uint32_t freq; + freq = s->timer[id].freq; + if (id > 1) { + s->timer[id].freq = 24000000 / + ((GET_PRESCALER(s->reg_tcfg[0], 1) + 1) * + (GET_DIVIDER(s->reg_tcfg[1], id))); + } else { + s->timer[id].freq = 24000000 / + ((GET_PRESCALER(s->reg_tcfg[0], 0) + 1) * + (GET_DIVIDER(s->reg_tcfg[1], id))); + } + + if (freq != s->timer[id].freq) { + ptimer_set_freq(s->timer[id].ptimer, s->timer[id].freq); + DPRINTF("freq=%dHz\n", s->timer[id].freq); + } +} + +/* + * Counter tick handler + */ +static void exynos4210_pwm_tick(void *opaque) +{ + Exynos4210PWM *s = (Exynos4210PWM *)opaque; + Exynos4210PWMState *p = (Exynos4210PWMState *)s->parent; + uint32_t id = s->id; + bool cmp; + + DPRINTF("timer %d tick\n", id); + + /* set irq status */ + p->reg_tint_cstat |= TINT_CSTAT_STATUS(id); + + /* raise IRQ */ + if (p->reg_tint_cstat & TINT_CSTAT_ENABLE(id)) { + DPRINTF("timer %d IRQ\n", id); + qemu_irq_raise(p->timer[id].irq); + } + + /* reload timer */ + if (id != 4) { + cmp = p->reg_tcon & TCON_TIMER_AUTO_RELOAD(id); + } else { + cmp = p->reg_tcon & TCON_TIMER4_AUTO_RELOAD; + } + + if (cmp) { + DPRINTF("auto reload timer %d count to %x\n", id, + p->timer[id].reg_tcntb); + ptimer_set_count(p->timer[id].ptimer, p->timer[id].reg_tcntb); + ptimer_run(p->timer[id].ptimer, 1); + } else { + /* stop timer, set status to STOP, see Basic Timer Operation */ + p->reg_tcon = ~TCON_TIMER_START(id); + ptimer_stop(p->timer[id].ptimer); + } +} + +/* + * PWM Read + */ +static uint64_t exynos4210_pwm_read(void *opaque, target_phys_addr_t offset, + unsigned size) +{ + Exynos4210PWMState *s = (Exynos4210PWMState *)opaque; + uint32_t value = 0; + int index; + + switch (offset) { + case TCFG0: case TCFG1: + index = (offset - TCFG0) >> 2; + value = s->reg_tcfg[index]; + break; + + case TCON: + value = s->reg_tcon; + break; + + case TCNTB0: case TCNTB1: + case TCNTB2: case TCNTB3: case TCNTB4: + index = (offset - TCNTB0) / 0xC; + value = s->timer[index].reg_tcntb; + break; + + case TCMPB0: case TCMPB1: + case TCMPB2: case TCMPB3: + index = (offset - TCMPB0) / 0xC; + value = s->timer[index].reg_tcmpb; + break; + + case TCNTO0: case TCNTO1: + case TCNTO2: case TCNTO3: case TCNTO4: + index = (offset == TCNTO4) ? 4 : (offset - TCNTO0) / 0xC; + value = ptimer_get_count(s->timer[index].ptimer); + break; + + case TINT_CSTAT: + value = s->reg_tint_cstat; + break; + + default: + fprintf(stderr, + "[exynos4210.pwm: bad read offset " TARGET_FMT_plx "]\n", + offset); + break; + } + return value; +} + +/* + * PWM Write + */ +static void exynos4210_pwm_write(void *opaque, target_phys_addr_t offset, + uint64_t value, unsigned size) +{ + Exynos4210PWMState *s = (Exynos4210PWMState *)opaque; + int index; + uint32_t new_val; + int i; + + switch (offset) { + case TCFG0: case TCFG1: + index = (offset - TCFG0) >> 2; + s->reg_tcfg[index] = value; + + /* update timers frequencies */ + for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { + exynos4210_pwm_update_freq(s, s->timer[i].id); + } + break; + + case TCON: + for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { + if ((value & TCON_TIMER_MANUAL_UPD(i)) > + (s->reg_tcon & TCON_TIMER_MANUAL_UPD(i))) { + /* + * TCNTB and TCMPB are loaded into TCNT and TCMP. + * Update timers. + */ + + /* this will start timer to run, this ok, because + * during processing start bit timer will be stopped + * if needed */ + ptimer_set_count(s->timer[i].ptimer, s->timer[i].reg_tcntb); + DPRINTF("set timer %d count to %x\n", i, + s->timer[i].reg_tcntb); + } + + if ((value & TCON_TIMER_START(i)) > + (s->reg_tcon & TCON_TIMER_START(i))) { + /* changed to start */ + ptimer_run(s->timer[i].ptimer, 1); + DPRINTF("run timer %d\n", i); + } + + if ((value & TCON_TIMER_START(i)) < + (s->reg_tcon & TCON_TIMER_START(i))) { + /* changed to stop */ + ptimer_stop(s->timer[i].ptimer); + DPRINTF("stop timer %d\n", i); + } + } + s->reg_tcon = value; + break; + + case TCNTB0: case TCNTB1: + case TCNTB2: case TCNTB3: case TCNTB4: + index = (offset - TCNTB0) / 0xC; + s->timer[index].reg_tcntb = value; + break; + + case TCMPB0: case TCMPB1: + case TCMPB2: case TCMPB3: + index = (offset - TCMPB0) / 0xC; + s->timer[index].reg_tcmpb = value; + break; + + case TINT_CSTAT: + new_val = (s->reg_tint_cstat & 0x3E0) + (0x1F & value); + new_val &= ~(0x3E0 & value); + + for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { + if ((new_val & TINT_CSTAT_STATUS(i)) < + (s->reg_tint_cstat & TINT_CSTAT_STATUS(i))) { + qemu_irq_lower(s->timer[i].irq); + } + } + + s->reg_tint_cstat = new_val; + break; + + default: + fprintf(stderr, + "[exynos4210.pwm: bad write offset " TARGET_FMT_plx "]\n", + offset); + break; + + } +} + +/* + * Set default values to timer fields and registers + */ +static void exynos4210_pwm_reset(DeviceState *d) +{ + Exynos4210PWMState *s = (Exynos4210PWMState *)d; + int i; + s->reg_tcfg[0] = 0x0101; + s->reg_tcfg[1] = 0x0; + s->reg_tcon = 0; + s->reg_tint_cstat = 0; + for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { + s->timer[i].reg_tcmpb = 0; + s->timer[i].reg_tcntb = 0; + + exynos4210_pwm_update_freq(s, s->timer[i].id); + ptimer_stop(s->timer[i].ptimer); + } +} + +static const MemoryRegionOps exynos4210_pwm_ops = { + .read = exynos4210_pwm_read, + .write = exynos4210_pwm_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +/* + * PWM timer initialization + */ +static int exynos4210_pwm_init(SysBusDevice *dev) +{ + Exynos4210PWMState *s = FROM_SYSBUS(Exynos4210PWMState, dev); + int i; + QEMUBH *bh; + + for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { + bh = qemu_bh_new(exynos4210_pwm_tick, &s->timer[i]); + sysbus_init_irq(dev, &s->timer[i].irq); + s->timer[i].ptimer = ptimer_init(bh); + s->timer[i].id = i; + s->timer[i].parent = s; + } + + memory_region_init_io(&s->iomem, &exynos4210_pwm_ops, s, "exynos4210-pwm", + EXYNOS4210_PWM_REG_MEM_SIZE); + sysbus_init_mmio(dev, &s->iomem); + + return 0; +} + +static void exynos4210_pwm_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = exynos4210_pwm_init; + dc->reset = exynos4210_pwm_reset; + dc->vmsd = &vmstate_exynos4210_pwm_state; +} + +static TypeInfo exynos4210_pwm_info = { + .name = "exynos4210.pwm", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Exynos4210PWMState), + .class_init = exynos4210_pwm_class_init, +}; + +static void exynos4210_pwm_register_types(void) +{ + type_register_static(&exynos4210_pwm_info); +} + +type_init(exynos4210_pwm_register_types) -- cgit v1.1 From df91b48f64075595e25a269befed5e4fe75501cb Mon Sep 17 00:00:00 2001 From: Maksim Kozlov Date: Thu, 16 Feb 2012 09:56:05 +0000 Subject: ARM: exynos4210: basic Power Management Unit implementation Patch adds basic model for Exynos4210 SoC PMU. This model implements PMU registers just as a bulk of memory. Currently, the only reason this device exists is that secondary CPU boot loader uses PMU INFORM5 register as a holding pen. Signed-off-by: Maksim Kozlov Signed-off-by: Evgeny Voevodin Signed-off-by: Peter Maydell --- hw/exynos4210.c | 9 + hw/exynos4210_pmu.c | 499 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 508 insertions(+) create mode 100644 hw/exynos4210_pmu.c (limited to 'hw') diff --git a/hw/exynos4210.c b/hw/exynos4210.c index 8ebe5df..f922f55 100644 --- a/hw/exynos4210.c +++ b/hw/exynos4210.c @@ -52,6 +52,9 @@ #define EXYNOS4210_EXT_COMBINER_BASE_ADDR 0x10440000 #define EXYNOS4210_INT_COMBINER_BASE_ADDR 0x10448000 +/* PMU SFR base address */ +#define EXYNOS4210_PMU_BASE_ADDR 0x10020000 + static uint8_t chipid_and_omr[] = { 0x11, 0x02, 0x21, 0x43, 0x09, 0x00, 0x00, 0x00 }; @@ -202,6 +205,12 @@ Exynos4210State *exynos4210_init(MemoryRegion *system_mem, memory_region_add_subregion(system_mem, EXYNOS4210_DRAM0_BASE_ADDR, &s->dram0_mem); + /* PMU. + * The only reason of existence at the moment is that secondary CPU boot + * loader uses PMU INFORM5 register as a holding pen. + */ + sysbus_create_simple("exynos4210.pmu", EXYNOS4210_PMU_BASE_ADDR, NULL); + /* PWM */ sysbus_create_varargs("exynos4210.pwm", EXYNOS4210_PWM_BASE_ADDR, s->irq_table[exynos4210_get_irq(22, 0)], diff --git a/hw/exynos4210_pmu.c b/hw/exynos4210_pmu.c new file mode 100644 index 0000000..c12d750 --- /dev/null +++ b/hw/exynos4210_pmu.c @@ -0,0 +1,499 @@ +/* + * Exynos4210 Power Management Unit (PMU) Emulation + * + * Copyright (C) 2011 Samsung Electronics Co Ltd. + * Maksim Kozlov + * + * 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 . + */ + +/* + * This model implements PMU registers just as a bulk of memory. Currently, + * the only reason this device exists is that secondary CPU boot loader + * uses PMU INFORM5 register as a holding pen. + */ + +#include "sysbus.h" + +#ifndef DEBUG_PMU +#define DEBUG_PMU 0 +#endif + +#ifndef DEBUG_PMU_EXTEND +#define DEBUG_PMU_EXTEND 0 +#endif + +#if DEBUG_PMU +#define PRINT_DEBUG(fmt, args...) \ + do { \ + fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ + } while (0) + +#if DEBUG_PMU_EXTEND +#define PRINT_DEBUG_EXTEND(fmt, args...) \ + do { \ + fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ + } while (0) +#else +#define PRINT_DEBUG_EXTEND(fmt, args...) do {} while (0) +#endif /* EXTEND */ + +#else +#define PRINT_DEBUG(fmt, args...) do {} while (0) +#define PRINT_DEBUG_EXTEND(fmt, args...) do {} while (0) +#endif + +/* + * Offsets for PMU registers + */ +#define OM_STAT 0x0000 /* OM status register */ +#define RTC_CLKO_SEL 0x000C /* Controls RTCCLKOUT */ +#define GNSS_RTC_OUT_CTRL 0x0010 /* Controls GNSS_RTC_OUT */ +/* Decides whether system-level low-power mode is used. */ +#define SYSTEM_POWER_DOWN_CTRL 0x0200 +/* Sets control options for CENTRAL_SEQ */ +#define SYSTEM_POWER_DOWN_OPTION 0x0208 +#define SWRESET 0x0400 /* Generate software reset */ +#define RST_STAT 0x0404 /* Reset status register */ +#define WAKEUP_STAT 0x0600 /* Wakeup status register */ +#define EINT_WAKEUP_MASK 0x0604 /* Configure External INTerrupt mask */ +#define WAKEUP_MASK 0x0608 /* Configure wakeup source mask */ +#define HDMI_PHY_CONTROL 0x0700 /* HDMI PHY control register */ +#define USBDEVICE_PHY_CONTROL 0x0704 /* USB Device PHY control register */ +#define USBHOST_PHY_CONTROL 0x0708 /* USB HOST PHY control register */ +#define DAC_PHY_CONTROL 0x070C /* DAC control register */ +#define MIPI_PHY0_CONTROL 0x0710 /* MIPI PHY control register */ +#define MIPI_PHY1_CONTROL 0x0714 /* MIPI PHY control register */ +#define ADC_PHY_CONTROL 0x0718 /* TS-ADC control register */ +#define PCIe_PHY_CONTROL 0x071C /* TS-PCIe control register */ +#define SATA_PHY_CONTROL 0x0720 /* TS-SATA control register */ +#define INFORM0 0x0800 /* Information register 0 */ +#define INFORM1 0x0804 /* Information register 1 */ +#define INFORM2 0x0808 /* Information register 2 */ +#define INFORM3 0x080C /* Information register 3 */ +#define INFORM4 0x0810 /* Information register 4 */ +#define INFORM5 0x0814 /* Information register 5 */ +#define INFORM6 0x0818 /* Information register 6 */ +#define INFORM7 0x081C /* Information register 7 */ +#define PMU_DEBUG 0x0A00 /* PMU debug register */ +/* Registers to set system-level low-power option */ +#define ARM_CORE0_SYS_PWR_REG 0x1000 +#define ARM_CORE1_SYS_PWR_REG 0x1010 +#define ARM_COMMON_SYS_PWR_REG 0x1080 +#define ARM_CPU_L2_0_SYS_PWR_REG 0x10C0 +#define ARM_CPU_L2_1_SYS_PWR_REG 0x10C4 +#define CMU_ACLKSTOP_SYS_PWR_REG 0x1100 +#define CMU_SCLKSTOP_SYS_PWR_REG 0x1104 +#define CMU_RESET_SYS_PWR_REG 0x110C +#define APLL_SYSCLK_SYS_PWR_REG 0x1120 +#define MPLL_SYSCLK_SYS_PWR_REG 0x1124 +#define VPLL_SYSCLK_SYS_PWR_REG 0x1128 +#define EPLL_SYSCLK_SYS_PWR_REG 0x112C +#define CMU_CLKSTOP_GPS_ALIVE_SYS_PWR_REG 0x1138 +#define CMU_RESET_GPS_ALIVE_SYS_PWR_REG 0x113C +#define CMU_CLKSTOP_CAM_SYS_PWR_REG 0x1140 +#define CMU_CLKSTOP_TV_SYS_PWR_REG 0x1144 +#define CMU_CLKSTOP_MFC_SYS_PWR_REG 0x1148 +#define CMU_CLKSTOP_G3D_SYS_PWR_REG 0x114C +#define CMU_CLKSTOP_LCD0_SYS_PWR_REG 0x1150 +#define CMU_CLKSTOP_LCD1_SYS_PWR_REG 0x1154 +#define CMU_CLKSTOP_MAUDIO_SYS_PWR_REG 0x1158 +#define CMU_CLKSTOP_GPS_SYS_PWR_REG 0x115C +#define CMU_RESET_CAM_SYS_PWR_REG 0x1160 +#define CMU_RESET_TV_SYS_PWR_REG 0x1164 +#define CMU_RESET_MFC_SYS_PWR_REG 0x1168 +#define CMU_RESET_G3D_SYS_PWR_REG 0x116C +#define CMU_RESET_LCD0_SYS_PWR_REG 0x1170 +#define CMU_RESET_LCD1_SYS_PWR_REG 0x1174 +#define CMU_RESET_MAUDIO_SYS_PWR_REG 0x1178 +#define CMU_RESET_GPS_SYS_PWR_REG 0x117C +#define TOP_BUS_SYS_PWR_REG 0x1180 +#define TOP_RETENTION_SYS_PWR_REG 0x1184 +#define TOP_PWR_SYS_PWR_REG 0x1188 +#define LOGIC_RESET_SYS_PWR_REG 0x11A0 +#define OneNANDXL_MEM_SYS_PWR_REG 0x11C0 +#define MODEMIF_MEM_SYS_PWR_REG 0x11C4 +#define USBDEVICE_MEM_SYS_PWR_REG 0x11CC +#define SDMMC_MEM_SYS_PWR_REG 0x11D0 +#define CSSYS_MEM_SYS_PWR_REG 0x11D4 +#define SECSS_MEM_SYS_PWR_REG 0x11D8 +#define PCIe_MEM_SYS_PWR_REG 0x11E0 +#define SATA_MEM_SYS_PWR_REG 0x11E4 +#define PAD_RETENTION_DRAM_SYS_PWR_REG 0x1200 +#define PAD_RETENTION_MAUDIO_SYS_PWR_REG 0x1204 +#define PAD_RETENTION_GPIO_SYS_PWR_REG 0x1220 +#define PAD_RETENTION_UART_SYS_PWR_REG 0x1224 +#define PAD_RETENTION_MMCA_SYS_PWR_REG 0x1228 +#define PAD_RETENTION_MMCB_SYS_PWR_REG 0x122C +#define PAD_RETENTION_EBIA_SYS_PWR_REG 0x1230 +#define PAD_RETENTION_EBIB_SYS_PWR_REG 0x1234 +#define PAD_ISOLATION_SYS_PWR_REG 0x1240 +#define PAD_ALV_SEL_SYS_PWR_REG 0x1260 +#define XUSBXTI_SYS_PWR_REG 0x1280 +#define XXTI_SYS_PWR_REG 0x1284 +#define EXT_REGULATOR_SYS_PWR_REG 0x12C0 +#define GPIO_MODE_SYS_PWR_REG 0x1300 +#define GPIO_MODE_MAUDIO_SYS_PWR_REG 0x1340 +#define CAM_SYS_PWR_REG 0x1380 +#define TV_SYS_PWR_REG 0x1384 +#define MFC_SYS_PWR_REG 0x1388 +#define G3D_SYS_PWR_REG 0x138C +#define LCD0_SYS_PWR_REG 0x1390 +#define LCD1_SYS_PWR_REG 0x1394 +#define MAUDIO_SYS_PWR_REG 0x1398 +#define GPS_SYS_PWR_REG 0x139C +#define GPS_ALIVE_SYS_PWR_REG 0x13A0 +#define ARM_CORE0_CONFIGURATION 0x2000 /* Configure power mode of ARM_CORE0 */ +#define ARM_CORE0_STATUS 0x2004 /* Check power mode of ARM_CORE0 */ +#define ARM_CORE0_OPTION 0x2008 /* Sets control options for ARM_CORE0 */ +#define ARM_CORE1_CONFIGURATION 0x2080 /* Configure power mode of ARM_CORE1 */ +#define ARM_CORE1_STATUS 0x2084 /* Check power mode of ARM_CORE1 */ +#define ARM_CORE1_OPTION 0x2088 /* Sets control options for ARM_CORE0 */ +#define ARM_COMMON_OPTION 0x2408 /* Sets control options for ARM_COMMON */ +/* Configure power mode of ARM_CPU_L2_0 */ +#define ARM_CPU_L2_0_CONFIGURATION 0x2600 +#define ARM_CPU_L2_0_STATUS 0x2604 /* Check power mode of ARM_CPU_L2_0 */ +/* Configure power mode of ARM_CPU_L2_1 */ +#define ARM_CPU_L2_1_CONFIGURATION 0x2620 +#define ARM_CPU_L2_1_STATUS 0x2624 /* Check power mode of ARM_CPU_L2_1 */ +/* Sets control options for PAD_RETENTION_MAUDIO */ +#define PAD_RETENTION_MAUDIO_OPTION 0x3028 +/* Sets control options for PAD_RETENTION_GPIO */ +#define PAD_RETENTION_GPIO_OPTION 0x3108 +/* Sets control options for PAD_RETENTION_UART */ +#define PAD_RETENTION_UART_OPTION 0x3128 +/* Sets control options for PAD_RETENTION_MMCA */ +#define PAD_RETENTION_MMCA_OPTION 0x3148 +/* Sets control options for PAD_RETENTION_MMCB */ +#define PAD_RETENTION_MMCB_OPTION 0x3168 +/* Sets control options for PAD_RETENTION_EBIA */ +#define PAD_RETENTION_EBIA_OPTION 0x3188 +/* Sets control options for PAD_RETENTION_EBIB */ +#define PAD_RETENTION_EBIB_OPTION 0x31A8 +#define PS_HOLD_CONTROL 0x330C /* PS_HOLD control register */ +#define XUSBXTI_CONFIGURATION 0x3400 /* Configure the pad of XUSBXTI */ +#define XUSBXTI_STATUS 0x3404 /* Check the pad of XUSBXTI */ +/* Sets time required for XUSBXTI to be stabilized */ +#define XUSBXTI_DURATION 0x341C +#define XXTI_CONFIGURATION 0x3420 /* Configure the pad of XXTI */ +#define XXTI_STATUS 0x3424 /* Check the pad of XXTI */ +/* Sets time required for XXTI to be stabilized */ +#define XXTI_DURATION 0x343C +/* Sets time required for EXT_REGULATOR to be stabilized */ +#define EXT_REGULATOR_DURATION 0x361C +#define CAM_CONFIGURATION 0x3C00 /* Configure power mode of CAM */ +#define CAM_STATUS 0x3C04 /* Check power mode of CAM */ +#define CAM_OPTION 0x3C08 /* Sets control options for CAM */ +#define TV_CONFIGURATION 0x3C20 /* Configure power mode of TV */ +#define TV_STATUS 0x3C24 /* Check power mode of TV */ +#define TV_OPTION 0x3C28 /* Sets control options for TV */ +#define MFC_CONFIGURATION 0x3C40 /* Configure power mode of MFC */ +#define MFC_STATUS 0x3C44 /* Check power mode of MFC */ +#define MFC_OPTION 0x3C48 /* Sets control options for MFC */ +#define G3D_CONFIGURATION 0x3C60 /* Configure power mode of G3D */ +#define G3D_STATUS 0x3C64 /* Check power mode of G3D */ +#define G3D_OPTION 0x3C68 /* Sets control options for G3D */ +#define LCD0_CONFIGURATION 0x3C80 /* Configure power mode of LCD0 */ +#define LCD0_STATUS 0x3C84 /* Check power mode of LCD0 */ +#define LCD0_OPTION 0x3C88 /* Sets control options for LCD0 */ +#define LCD1_CONFIGURATION 0x3CA0 /* Configure power mode of LCD1 */ +#define LCD1_STATUS 0x3CA4 /* Check power mode of LCD1 */ +#define LCD1_OPTION 0x3CA8 /* Sets control options for LCD1 */ +#define GPS_CONFIGURATION 0x3CE0 /* Configure power mode of GPS */ +#define GPS_STATUS 0x3CE4 /* Check power mode of GPS */ +#define GPS_OPTION 0x3CE8 /* Sets control options for GPS */ +#define GPS_ALIVE_CONFIGURATION 0x3D00 /* Configure power mode of GPS */ +#define GPS_ALIVE_STATUS 0x3D04 /* Check power mode of GPS */ +#define GPS_ALIVE_OPTION 0x3D08 /* Sets control options for GPS */ + +#define EXYNOS4210_PMU_REGS_MEM_SIZE 0x3d0c + +typedef struct Exynos4210PmuReg { + const char *name; /* for debug only */ + uint32_t offset; + uint32_t reset_value; +} Exynos4210PmuReg; + +static const Exynos4210PmuReg exynos4210_pmu_regs[] = { + {"OM_STAT", OM_STAT, 0x00000000}, + {"RTC_CLKO_SEL", RTC_CLKO_SEL, 0x00000000}, + {"GNSS_RTC_OUT_CTRL", GNSS_RTC_OUT_CTRL, 0x00000001}, + {"SYSTEM_POWER_DOWN_CTRL", SYSTEM_POWER_DOWN_CTRL, 0x00010000}, + {"SYSTEM_POWER_DOWN_OPTION", SYSTEM_POWER_DOWN_OPTION, 0x03030000}, + {"SWRESET", SWRESET, 0x00000000}, + {"RST_STAT", RST_STAT, 0x00000000}, + {"WAKEUP_STAT", WAKEUP_STAT, 0x00000000}, + {"EINT_WAKEUP_MASK", EINT_WAKEUP_MASK, 0x00000000}, + {"WAKEUP_MASK", WAKEUP_MASK, 0x00000000}, + {"HDMI_PHY_CONTROL", HDMI_PHY_CONTROL, 0x00960000}, + {"USBDEVICE_PHY_CONTROL", USBDEVICE_PHY_CONTROL, 0x00000000}, + {"USBHOST_PHY_CONTROL", USBHOST_PHY_CONTROL, 0x00000000}, + {"DAC_PHY_CONTROL", DAC_PHY_CONTROL, 0x00000000}, + {"MIPI_PHY0_CONTROL", MIPI_PHY0_CONTROL, 0x00000000}, + {"MIPI_PHY1_CONTROL", MIPI_PHY1_CONTROL, 0x00000000}, + {"ADC_PHY_CONTROL", ADC_PHY_CONTROL, 0x00000001}, + {"PCIe_PHY_CONTROL", PCIe_PHY_CONTROL, 0x00000000}, + {"SATA_PHY_CONTROL", SATA_PHY_CONTROL, 0x00000000}, + {"INFORM0", INFORM0, 0x00000000}, + {"INFORM1", INFORM1, 0x00000000}, + {"INFORM2", INFORM2, 0x00000000}, + {"INFORM3", INFORM3, 0x00000000}, + {"INFORM4", INFORM4, 0x00000000}, + {"INFORM5", INFORM5, 0x00000000}, + {"INFORM6", INFORM6, 0x00000000}, + {"INFORM7", INFORM7, 0x00000000}, + {"PMU_DEBUG", PMU_DEBUG, 0x00000000}, + {"ARM_CORE0_SYS_PWR_REG", ARM_CORE0_SYS_PWR_REG, 0xFFFFFFFF}, + {"ARM_CORE1_SYS_PWR_REG", ARM_CORE1_SYS_PWR_REG, 0xFFFFFFFF}, + {"ARM_COMMON_SYS_PWR_REG", ARM_COMMON_SYS_PWR_REG, 0xFFFFFFFF}, + {"ARM_CPU_L2_0_SYS_PWR_REG", ARM_CPU_L2_0_SYS_PWR_REG, 0xFFFFFFFF}, + {"ARM_CPU_L2_1_SYS_PWR_REG", ARM_CPU_L2_1_SYS_PWR_REG, 0xFFFFFFFF}, + {"CMU_ACLKSTOP_SYS_PWR_REG", CMU_ACLKSTOP_SYS_PWR_REG, 0xFFFFFFFF}, + {"CMU_SCLKSTOP_SYS_PWR_REG", CMU_SCLKSTOP_SYS_PWR_REG, 0xFFFFFFFF}, + {"CMU_RESET_SYS_PWR_REG", CMU_RESET_SYS_PWR_REG, 0xFFFFFFFF}, + {"APLL_SYSCLK_SYS_PWR_REG", APLL_SYSCLK_SYS_PWR_REG, 0xFFFFFFFF}, + {"MPLL_SYSCLK_SYS_PWR_REG", MPLL_SYSCLK_SYS_PWR_REG, 0xFFFFFFFF}, + {"VPLL_SYSCLK_SYS_PWR_REG", VPLL_SYSCLK_SYS_PWR_REG, 0xFFFFFFFF}, + {"EPLL_SYSCLK_SYS_PWR_REG", EPLL_SYSCLK_SYS_PWR_REG, 0xFFFFFFFF}, + {"CMU_CLKSTOP_GPS_ALIVE_SYS_PWR_REG", CMU_CLKSTOP_GPS_ALIVE_SYS_PWR_REG, + 0xFFFFFFFF}, + {"CMU_RESET_GPS_ALIVE_SYS_PWR_REG", CMU_RESET_GPS_ALIVE_SYS_PWR_REG, + 0xFFFFFFFF}, + {"CMU_CLKSTOP_CAM_SYS_PWR_REG", CMU_CLKSTOP_CAM_SYS_PWR_REG, 0xFFFFFFFF}, + {"CMU_CLKSTOP_TV_SYS_PWR_REG", CMU_CLKSTOP_TV_SYS_PWR_REG, 0xFFFFFFFF}, + {"CMU_CLKSTOP_MFC_SYS_PWR_REG", CMU_CLKSTOP_MFC_SYS_PWR_REG, 0xFFFFFFFF}, + {"CMU_CLKSTOP_G3D_SYS_PWR_REG", CMU_CLKSTOP_G3D_SYS_PWR_REG, 0xFFFFFFFF}, + {"CMU_CLKSTOP_LCD0_SYS_PWR_REG", CMU_CLKSTOP_LCD0_SYS_PWR_REG, 0xFFFFFFFF}, + {"CMU_CLKSTOP_LCD1_SYS_PWR_REG", CMU_CLKSTOP_LCD1_SYS_PWR_REG, 0xFFFFFFFF}, + {"CMU_CLKSTOP_MAUDIO_SYS_PWR_REG", CMU_CLKSTOP_MAUDIO_SYS_PWR_REG, + 0xFFFFFFFF}, + {"CMU_CLKSTOP_GPS_SYS_PWR_REG", CMU_CLKSTOP_GPS_SYS_PWR_REG, 0xFFFFFFFF}, + {"CMU_RESET_CAM_SYS_PWR_REG", CMU_RESET_CAM_SYS_PWR_REG, 0xFFFFFFFF}, + {"CMU_RESET_TV_SYS_PWR_REG", CMU_RESET_TV_SYS_PWR_REG, 0xFFFFFFFF}, + {"CMU_RESET_MFC_SYS_PWR_REG", CMU_RESET_MFC_SYS_PWR_REG, 0xFFFFFFFF}, + {"CMU_RESET_G3D_SYS_PWR_REG", CMU_RESET_G3D_SYS_PWR_REG, 0xFFFFFFFF}, + {"CMU_RESET_LCD0_SYS_PWR_REG", CMU_RESET_LCD0_SYS_PWR_REG, 0xFFFFFFFF}, + {"CMU_RESET_LCD1_SYS_PWR_REG", CMU_RESET_LCD1_SYS_PWR_REG, 0xFFFFFFFF}, + {"CMU_RESET_MAUDIO_SYS_PWR_REG", CMU_RESET_MAUDIO_SYS_PWR_REG, 0xFFFFFFFF}, + {"CMU_RESET_GPS_SYS_PWR_REG", CMU_RESET_GPS_SYS_PWR_REG, 0xFFFFFFFF}, + {"TOP_BUS_SYS_PWR_REG", TOP_BUS_SYS_PWR_REG, 0xFFFFFFFF}, + {"TOP_RETENTION_SYS_PWR_REG", TOP_RETENTION_SYS_PWR_REG, 0xFFFFFFFF}, + {"TOP_PWR_SYS_PWR_REG", TOP_PWR_SYS_PWR_REG, 0xFFFFFFFF}, + {"LOGIC_RESET_SYS_PWR_REG", LOGIC_RESET_SYS_PWR_REG, 0xFFFFFFFF}, + {"OneNANDXL_MEM_SYS_PWR_REG", OneNANDXL_MEM_SYS_PWR_REG, 0xFFFFFFFF}, + {"MODEMIF_MEM_SYS_PWR_REG", MODEMIF_MEM_SYS_PWR_REG, 0xFFFFFFFF}, + {"USBDEVICE_MEM_SYS_PWR_REG", USBDEVICE_MEM_SYS_PWR_REG, 0xFFFFFFFF}, + {"SDMMC_MEM_SYS_PWR_REG", SDMMC_MEM_SYS_PWR_REG, 0xFFFFFFFF}, + {"CSSYS_MEM_SYS_PWR_REG", CSSYS_MEM_SYS_PWR_REG, 0xFFFFFFFF}, + {"SECSS_MEM_SYS_PWR_REG", SECSS_MEM_SYS_PWR_REG, 0xFFFFFFFF}, + {"PCIe_MEM_SYS_PWR_REG", PCIe_MEM_SYS_PWR_REG, 0xFFFFFFFF}, + {"SATA_MEM_SYS_PWR_REG", SATA_MEM_SYS_PWR_REG, 0xFFFFFFFF}, + {"PAD_RETENTION_DRAM_SYS_PWR_REG", PAD_RETENTION_DRAM_SYS_PWR_REG, + 0xFFFFFFFF}, + {"PAD_RETENTION_MAUDIO_SYS_PWR_REG", PAD_RETENTION_MAUDIO_SYS_PWR_REG, + 0xFFFFFFFF}, + {"PAD_RETENTION_GPIO_SYS_PWR_REG", PAD_RETENTION_GPIO_SYS_PWR_REG, + 0xFFFFFFFF}, + {"PAD_RETENTION_UART_SYS_PWR_REG", PAD_RETENTION_UART_SYS_PWR_REG, + 0xFFFFFFFF}, + {"PAD_RETENTION_MMCA_SYS_PWR_REG", PAD_RETENTION_MMCA_SYS_PWR_REG, + 0xFFFFFFFF}, + {"PAD_RETENTION_MMCB_SYS_PWR_REG", PAD_RETENTION_MMCB_SYS_PWR_REG, + 0xFFFFFFFF}, + {"PAD_RETENTION_EBIA_SYS_PWR_REG", PAD_RETENTION_EBIA_SYS_PWR_REG, + 0xFFFFFFFF}, + {"PAD_RETENTION_EBIB_SYS_PWR_REG", PAD_RETENTION_EBIB_SYS_PWR_REG, + 0xFFFFFFFF}, + {"PAD_ISOLATION_SYS_PWR_REG", PAD_ISOLATION_SYS_PWR_REG, 0xFFFFFFFF}, + {"PAD_ALV_SEL_SYS_PWR_REG", PAD_ALV_SEL_SYS_PWR_REG, 0xFFFFFFFF}, + {"XUSBXTI_SYS_PWR_REG", XUSBXTI_SYS_PWR_REG, 0xFFFFFFFF}, + {"XXTI_SYS_PWR_REG", XXTI_SYS_PWR_REG, 0xFFFFFFFF}, + {"EXT_REGULATOR_SYS_PWR_REG", EXT_REGULATOR_SYS_PWR_REG, 0xFFFFFFFF}, + {"GPIO_MODE_SYS_PWR_REG", GPIO_MODE_SYS_PWR_REG, 0xFFFFFFFF}, + {"GPIO_MODE_MAUDIO_SYS_PWR_REG", GPIO_MODE_MAUDIO_SYS_PWR_REG, 0xFFFFFFFF}, + {"CAM_SYS_PWR_REG", CAM_SYS_PWR_REG, 0xFFFFFFFF}, + {"TV_SYS_PWR_REG", TV_SYS_PWR_REG, 0xFFFFFFFF}, + {"MFC_SYS_PWR_REG", MFC_SYS_PWR_REG, 0xFFFFFFFF}, + {"G3D_SYS_PWR_REG", G3D_SYS_PWR_REG, 0xFFFFFFFF}, + {"LCD0_SYS_PWR_REG", LCD0_SYS_PWR_REG, 0xFFFFFFFF}, + {"LCD1_SYS_PWR_REG", LCD1_SYS_PWR_REG, 0xFFFFFFFF}, + {"MAUDIO_SYS_PWR_REG", MAUDIO_SYS_PWR_REG, 0xFFFFFFFF}, + {"GPS_SYS_PWR_REG", GPS_SYS_PWR_REG, 0xFFFFFFFF}, + {"GPS_ALIVE_SYS_PWR_REG", GPS_ALIVE_SYS_PWR_REG, 0xFFFFFFFF}, + {"ARM_CORE0_CONFIGURATION", ARM_CORE0_CONFIGURATION, 0x00000003}, + {"ARM_CORE0_STATUS", ARM_CORE0_STATUS, 0x00030003}, + {"ARM_CORE0_OPTION", ARM_CORE0_OPTION, 0x01010001}, + {"ARM_CORE1_CONFIGURATION", ARM_CORE1_CONFIGURATION, 0x00000003}, + {"ARM_CORE1_STATUS", ARM_CORE1_STATUS, 0x00030003}, + {"ARM_CORE1_OPTION", ARM_CORE1_OPTION, 0x01010001}, + {"ARM_COMMON_OPTION", ARM_COMMON_OPTION, 0x00000001}, + {"ARM_CPU_L2_0_CONFIGURATION", ARM_CPU_L2_0_CONFIGURATION, 0x00000003}, + {"ARM_CPU_L2_0_STATUS", ARM_CPU_L2_0_STATUS, 0x00000003}, + {"ARM_CPU_L2_1_CONFIGURATION", ARM_CPU_L2_1_CONFIGURATION, 0x00000003}, + {"ARM_CPU_L2_1_STATUS", ARM_CPU_L2_1_STATUS, 0x00000003}, + {"PAD_RETENTION_MAUDIO_OPTION", PAD_RETENTION_MAUDIO_OPTION, 0x00000000}, + {"PAD_RETENTION_GPIO_OPTION", PAD_RETENTION_GPIO_OPTION, 0x00000000}, + {"PAD_RETENTION_UART_OPTION", PAD_RETENTION_UART_OPTION, 0x00000000}, + {"PAD_RETENTION_MMCA_OPTION", PAD_RETENTION_MMCA_OPTION, 0x00000000}, + {"PAD_RETENTION_MMCB_OPTION", PAD_RETENTION_MMCB_OPTION, 0x00000000}, + {"PAD_RETENTION_EBIA_OPTION", PAD_RETENTION_EBIA_OPTION, 0x00000000}, + {"PAD_RETENTION_EBIB_OPTION", PAD_RETENTION_EBIB_OPTION, 0x00000000}, + {"PS_HOLD_CONTROL", PS_HOLD_CONTROL, 0x00005200}, + {"XUSBXTI_CONFIGURATION", XUSBXTI_CONFIGURATION, 0x00000001}, + {"XUSBXTI_STATUS", XUSBXTI_STATUS, 0x00000001}, + {"XUSBXTI_DURATION", XUSBXTI_DURATION, 0xFFF00000}, + {"XXTI_CONFIGURATION", XXTI_CONFIGURATION, 0x00000001}, + {"XXTI_STATUS", XXTI_STATUS, 0x00000001}, + {"XXTI_DURATION", XXTI_DURATION, 0xFFF00000}, + {"EXT_REGULATOR_DURATION", EXT_REGULATOR_DURATION, 0xFFF03FFF}, + {"CAM_CONFIGURATION", CAM_CONFIGURATION, 0x00000007}, + {"CAM_STATUS", CAM_STATUS, 0x00060007}, + {"CAM_OPTION", CAM_OPTION, 0x00000001}, + {"TV_CONFIGURATION", TV_CONFIGURATION, 0x00000007}, + {"TV_STATUS", TV_STATUS, 0x00060007}, + {"TV_OPTION", TV_OPTION, 0x00000001}, + {"MFC_CONFIGURATION", MFC_CONFIGURATION, 0x00000007}, + {"MFC_STATUS", MFC_STATUS, 0x00060007}, + {"MFC_OPTION", MFC_OPTION, 0x00000001}, + {"G3D_CONFIGURATION", G3D_CONFIGURATION, 0x00000007}, + {"G3D_STATUS", G3D_STATUS, 0x00060007}, + {"G3D_OPTION", G3D_OPTION, 0x00000001}, + {"LCD0_CONFIGURATION", LCD0_CONFIGURATION, 0x00000007}, + {"LCD0_STATUS", LCD0_STATUS, 0x00060007}, + {"LCD0_OPTION", LCD0_OPTION, 0x00000001}, + {"LCD1_CONFIGURATION", LCD1_CONFIGURATION, 0x00000007}, + {"LCD1_STATUS", LCD1_STATUS, 0x00060007}, + {"LCD1_OPTION", LCD1_OPTION, 0x00000001}, + {"GPS_CONFIGURATION", GPS_CONFIGURATION, 0x00000007}, + {"GPS_STATUS", GPS_STATUS, 0x00060007}, + {"GPS_OPTION", GPS_OPTION, 0x00000001}, + {"GPS_ALIVE_CONFIGURATION", GPS_ALIVE_CONFIGURATION, 0x00000007}, + {"GPS_ALIVE_STATUS", GPS_ALIVE_STATUS, 0x00060007}, + {"GPS_ALIVE_OPTION", GPS_ALIVE_OPTION, 0x00000001}, +}; + +#define PMU_NUM_OF_REGISTERS \ + (sizeof(exynos4210_pmu_regs) / sizeof(Exynos4210PmuReg)) + +typedef struct Exynos4210PmuState { + SysBusDevice busdev; + MemoryRegion iomem; + uint32_t reg[PMU_NUM_OF_REGISTERS]; +} Exynos4210PmuState; + +static uint64_t exynos4210_pmu_read(void *opaque, target_phys_addr_t offset, + unsigned size) +{ + Exynos4210PmuState *s = (Exynos4210PmuState *)opaque; + unsigned i; + const Exynos4210PmuReg *reg_p = exynos4210_pmu_regs; + + for (i = 0; i < PMU_NUM_OF_REGISTERS; i++) { + if (reg_p->offset == offset) { + PRINT_DEBUG_EXTEND("%s [0x%04x] -> 0x%04x\n", reg_p->name, + (uint32_t)offset, s->reg[i]); + return s->reg[i]; + } + reg_p++; + } + PRINT_DEBUG("QEMU PMU ERROR: bad read offset 0x%04x\n", (uint32_t)offset); + return 0; +} + +static void exynos4210_pmu_write(void *opaque, target_phys_addr_t offset, + uint64_t val, unsigned size) +{ + Exynos4210PmuState *s = (Exynos4210PmuState *)opaque; + unsigned i; + const Exynos4210PmuReg *reg_p = exynos4210_pmu_regs; + + for (i = 0; i < PMU_NUM_OF_REGISTERS; i++) { + if (reg_p->offset == offset) { + PRINT_DEBUG_EXTEND("%s <0x%04x> <- 0x%04x\n", reg_p->name, + (uint32_t)offset, (uint32_t)val); + s->reg[i] = val; + return; + } + reg_p++; + } + PRINT_DEBUG("QEMU PMU ERROR: bad write offset 0x%04x\n", (uint32_t)offset); +} + +static const MemoryRegionOps exynos4210_pmu_ops = { + .read = exynos4210_pmu_read, + .write = exynos4210_pmu_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false + } +}; + +static void exynos4210_pmu_reset(DeviceState *dev) +{ + Exynos4210PmuState *s = + container_of(dev, Exynos4210PmuState, busdev.qdev); + unsigned i; + + /* Set default values for registers */ + for (i = 0; i < PMU_NUM_OF_REGISTERS; i++) { + s->reg[i] = exynos4210_pmu_regs[i].reset_value; + } +} + +static int exynos4210_pmu_init(SysBusDevice *dev) +{ + Exynos4210PmuState *s = FROM_SYSBUS(Exynos4210PmuState, dev); + + /* memory mapping */ + memory_region_init_io(&s->iomem, &exynos4210_pmu_ops, s, "exynos4210.pmu", + EXYNOS4210_PMU_REGS_MEM_SIZE); + sysbus_init_mmio(dev, &s->iomem); + return 0; +} + +static const VMStateDescription exynos4210_pmu_vmstate = { + .name = "exynos4210.pmu", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(reg, Exynos4210PmuState, PMU_NUM_OF_REGISTERS), + VMSTATE_END_OF_LIST() + } +}; + +static void exynos4210_pmu_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = exynos4210_pmu_init; + dc->reset = exynos4210_pmu_reset; + dc->vmsd = &exynos4210_pmu_vmstate; +} + +static TypeInfo exynos4210_pmu_info = { + .name = "exynos4210.pmu", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Exynos4210PmuState), + .class_init = exynos4210_pmu_class_init, +}; + +static void exynos4210_pmu_register(void) +{ + type_register_static(&exynos4210_pmu_info); +} + +type_init(exynos4210_pmu_register) -- cgit v1.1 From 12c775db14c037715e31079e2c17899abd0a2aa6 Mon Sep 17 00:00:00 2001 From: Evgeny Voevodin Date: Thu, 16 Feb 2012 09:56:05 +0000 Subject: ARM: exynos4210: MCT support. Signed-off-by: Evgeny Voevodin Signed-off-by: Peter Maydell --- hw/exynos4210.c | 19 + hw/exynos4210_mct.c | 1488 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1507 insertions(+) create mode 100644 hw/exynos4210_mct.c (limited to 'hw') diff --git a/hw/exynos4210.c b/hw/exynos4210.c index f922f55..558f669 100644 --- a/hw/exynos4210.c +++ b/hw/exynos4210.c @@ -32,6 +32,9 @@ /* PWM */ #define EXYNOS4210_PWM_BASE_ADDR 0x139D0000 +/* MCT */ +#define EXYNOS4210_MCT_BASE_ADDR 0x10050000 + /* UART's definitions */ #define EXYNOS4210_UART0_BASE_ADDR 0x13800000 #define EXYNOS4210_UART1_BASE_ADDR 0x13810000 @@ -220,6 +223,22 @@ Exynos4210State *exynos4210_init(MemoryRegion *system_mem, s->irq_table[exynos4210_get_irq(22, 4)], NULL); + /* Multi Core Timer */ + dev = qdev_create(NULL, "exynos4210.mct"); + qdev_init_nofail(dev); + busdev = sysbus_from_qdev(dev); + for (n = 0; n < 4; n++) { + /* Connect global timer interrupts to Combiner gpio_in */ + sysbus_connect_irq(busdev, n, + s->irq_table[exynos4210_get_irq(1, 4 + n)]); + } + /* Connect local timer interrupts to Combiner gpio_in */ + sysbus_connect_irq(busdev, 4, + s->irq_table[exynos4210_get_irq(51, 0)]); + sysbus_connect_irq(busdev, 5, + s->irq_table[exynos4210_get_irq(35, 3)]); + sysbus_mmio_map(busdev, 0, EXYNOS4210_MCT_BASE_ADDR); + /*** UARTs ***/ exynos4210_uart_create(EXYNOS4210_UART0_BASE_ADDR, EXYNOS4210_UART0_FIFO_SIZE, 0, NULL, diff --git a/hw/exynos4210_mct.c b/hw/exynos4210_mct.c new file mode 100644 index 0000000..01e3fb8 --- /dev/null +++ b/hw/exynos4210_mct.c @@ -0,0 +1,1488 @@ +/* + * Samsung exynos4210 Multi Core timer + * + * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. + * All rights reserved. + * + * Evgeny Voevodin + * + * 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 . + */ + +/* + * Global Timer: + * + * Consists of two timers. First represents Free Running Counter and second + * is used to measure interval from FRC to nearest comparator. + * + * 0 UINT64_MAX + * | timer0 | + * | <-------------------------------------------------------------- | + * | --------------------------------------------frc---------------> | + * |______________________________________________|__________________| + * CMP0 CMP1 CMP2 | CMP3 + * __| |_ + * | timer1 | + * | -------------> | + * frc CMPx + * + * Problem: when implementing global timer as is, overflow arises. + * next_time = cur_time + period * count; + * period and count are 64 bits width. + * Lets arm timer for MCT_GT_COUNTER_STEP count and update internal G_CNT + * register during each event. + * + * Problem: both timers need to be implemented using MCT_XT_COUNTER_STEP because + * local timer contains two counters: TCNT and ICNT. TCNT == 0 -> ICNT--. + * IRQ is generated when ICNT riches zero. Implementation where TCNT == 0 + * generates IRQs suffers from too frequently events. Better to have one + * uint64_t counter equal to TCNT*ICNT and arm ptimer.c for a minimum(TCNT*ICNT, + * MCT_GT_COUNTER_STEP); (yes, if target tunes ICNT * TCNT to be too low values, + * there is no way to avoid frequently events). + */ + +#include "sysbus.h" +#include "qemu-timer.h" +#include "qemu-common.h" +#include "ptimer.h" + +#include "exynos4210.h" + +//#define DEBUG_MCT + +#ifdef DEBUG_MCT +#define DPRINTF(fmt, ...) \ + do { fprintf(stdout, "MCT: [%24s:%5d] " fmt, __func__, __LINE__, \ + ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do {} while (0) +#endif + +#define MCT_CFG 0x000 +#define G_CNT_L 0x100 +#define G_CNT_U 0x104 +#define G_CNT_WSTAT 0x110 +#define G_COMP0_L 0x200 +#define G_COMP0_U 0x204 +#define G_COMP0_ADD_INCR 0x208 +#define G_COMP1_L 0x210 +#define G_COMP1_U 0x214 +#define G_COMP1_ADD_INCR 0x218 +#define G_COMP2_L 0x220 +#define G_COMP2_U 0x224 +#define G_COMP2_ADD_INCR 0x228 +#define G_COMP3_L 0x230 +#define G_COMP3_U 0x234 +#define G_COMP3_ADD_INCR 0x238 +#define G_TCON 0x240 +#define G_INT_CSTAT 0x244 +#define G_INT_ENB 0x248 +#define G_WSTAT 0x24C +#define L0_TCNTB 0x300 +#define L0_TCNTO 0x304 +#define L0_ICNTB 0x308 +#define L0_ICNTO 0x30C +#define L0_FRCNTB 0x310 +#define L0_FRCNTO 0x314 +#define L0_TCON 0x320 +#define L0_INT_CSTAT 0x330 +#define L0_INT_ENB 0x334 +#define L0_WSTAT 0x340 +#define L1_TCNTB 0x400 +#define L1_TCNTO 0x404 +#define L1_ICNTB 0x408 +#define L1_ICNTO 0x40C +#define L1_FRCNTB 0x410 +#define L1_FRCNTO 0x414 +#define L1_TCON 0x420 +#define L1_INT_CSTAT 0x430 +#define L1_INT_ENB 0x434 +#define L1_WSTAT 0x440 + +#define MCT_CFG_GET_PRESCALER(x) ((x) & 0xFF) +#define MCT_CFG_GET_DIVIDER(x) (1 << ((x) >> 8 & 7)) + +#define GET_G_COMP_IDX(offset) (((offset) - G_COMP0_L) / 0x10) +#define GET_G_COMP_ADD_INCR_IDX(offset) (((offset) - G_COMP0_ADD_INCR) / 0x10) + +#define G_COMP_L(x) (G_COMP0_L + (x) * 0x10) +#define G_COMP_U(x) (G_COMP0_U + (x) * 0x10) + +#define G_COMP_ADD_INCR(x) (G_COMP0_ADD_INCR + (x) * 0x10) + +/* MCT bits */ +#define G_TCON_COMP_ENABLE(x) (1 << 2 * (x)) +#define G_TCON_AUTO_ICREMENT(x) (1 << (2 * (x) + 1)) +#define G_TCON_TIMER_ENABLE (1 << 8) + +#define G_INT_ENABLE(x) (1 << (x)) +#define G_INT_CSTAT_COMP(x) (1 << (x)) + +#define G_CNT_WSTAT_L 1 +#define G_CNT_WSTAT_U 2 + +#define G_WSTAT_COMP_L(x) (1 << 4 * (x)) +#define G_WSTAT_COMP_U(x) (1 << ((4 * (x)) + 1)) +#define G_WSTAT_COMP_ADDINCR(x) (1 << ((4 * (x)) + 2)) +#define G_WSTAT_TCON_WRITE (1 << 16) + +#define GET_L_TIMER_IDX(offset) ((((offset) & 0xF00) - L0_TCNTB) / 0x100) +#define GET_L_TIMER_CNT_REG_IDX(offset, lt_i) \ + (((offset) - (L0_TCNTB + 0x100 * (lt_i))) >> 2) + +#define L_ICNTB_MANUAL_UPDATE (1 << 31) + +#define L_TCON_TICK_START (1) +#define L_TCON_INT_START (1 << 1) +#define L_TCON_INTERVAL_MODE (1 << 2) +#define L_TCON_FRC_START (1 << 3) + +#define L_INT_CSTAT_INTCNT (1 << 0) +#define L_INT_CSTAT_FRCCNT (1 << 1) + +#define L_INT_INTENB_ICNTEIE (1 << 0) +#define L_INT_INTENB_FRCEIE (1 << 1) + +#define L_WSTAT_TCNTB_WRITE (1 << 0) +#define L_WSTAT_ICNTB_WRITE (1 << 1) +#define L_WSTAT_FRCCNTB_WRITE (1 << 2) +#define L_WSTAT_TCON_WRITE (1 << 3) + +enum LocalTimerRegCntIndexes { + L_REG_CNT_TCNTB, + L_REG_CNT_TCNTO, + L_REG_CNT_ICNTB, + L_REG_CNT_ICNTO, + L_REG_CNT_FRCCNTB, + L_REG_CNT_FRCCNTO, + + L_REG_CNT_AMOUNT +}; + +#define MCT_NIRQ 6 +#define MCT_SFR_SIZE 0x444 + +#define MCT_GT_CMP_NUM 4 + +#define MCT_GT_MAX_VAL UINT64_MAX + +#define MCT_GT_COUNTER_STEP 0x100000000ULL +#define MCT_LT_COUNTER_STEP 0x100000000ULL +#define MCT_LT_CNT_LOW_LIMIT 0x100 + +/* global timer */ +typedef struct { + qemu_irq irq[MCT_GT_CMP_NUM]; + + struct gregs { + uint64_t cnt; + uint32_t cnt_wstat; + uint32_t tcon; + uint32_t int_cstat; + uint32_t int_enb; + uint32_t wstat; + uint64_t comp[MCT_GT_CMP_NUM]; + uint32_t comp_add_incr[MCT_GT_CMP_NUM]; + } reg; + + uint64_t count; /* Value FRC was armed with */ + int32_t curr_comp; /* Current comparator FRC is running to */ + + ptimer_state *ptimer_frc; /* FRC timer */ + +} Exynos4210MCTGT; + +/* local timer */ +typedef struct { + int id; /* timer id */ + qemu_irq irq; /* local timer irq */ + + struct tick_timer { + uint32_t cnt_run; /* cnt timer is running */ + uint32_t int_run; /* int timer is running */ + + uint32_t last_icnto; + uint32_t last_tcnto; + uint32_t tcntb; /* initial value for TCNTB */ + uint32_t icntb; /* initial value for ICNTB */ + + /* for step mode */ + uint64_t distance; /* distance to count to the next event */ + uint64_t progress; /* progress when counting by steps */ + uint64_t count; /* count to arm timer with */ + + ptimer_state *ptimer_tick; /* timer for tick counter */ + } tick_timer; + + /* use ptimer.c to represent count down timer */ + + ptimer_state *ptimer_frc; /* timer for free running counter */ + + /* registers */ + struct lregs { + uint32_t cnt[L_REG_CNT_AMOUNT]; + uint32_t tcon; + uint32_t int_cstat; + uint32_t int_enb; + uint32_t wstat; + } reg; + +} Exynos4210MCTLT; + +typedef struct Exynos4210MCTState { + SysBusDevice busdev; + MemoryRegion iomem; + + /* Registers */ + uint32_t reg_mct_cfg; + + Exynos4210MCTLT l_timer[2]; + Exynos4210MCTGT g_timer; + + uint32_t freq; /* all timers tick frequency, TCLK */ +} Exynos4210MCTState; + +/*** VMState ***/ +static const VMStateDescription vmstate_tick_timer = { + .name = "exynos4210.mct.tick_timer", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(cnt_run, struct tick_timer), + VMSTATE_UINT32(int_run, struct tick_timer), + VMSTATE_UINT32(last_icnto, struct tick_timer), + VMSTATE_UINT32(last_tcnto, struct tick_timer), + VMSTATE_UINT32(tcntb, struct tick_timer), + VMSTATE_UINT32(icntb, struct tick_timer), + VMSTATE_UINT64(distance, struct tick_timer), + VMSTATE_UINT64(progress, struct tick_timer), + VMSTATE_UINT64(count, struct tick_timer), + VMSTATE_PTIMER(ptimer_tick, struct tick_timer), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_lregs = { + .name = "exynos4210.mct.lregs", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(cnt, struct lregs, L_REG_CNT_AMOUNT), + VMSTATE_UINT32(tcon, struct lregs), + VMSTATE_UINT32(int_cstat, struct lregs), + VMSTATE_UINT32(int_enb, struct lregs), + VMSTATE_UINT32(wstat, struct lregs), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_exynos4210_mct_lt = { + .name = "exynos4210.mct.lt", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_INT32(id, Exynos4210MCTLT), + VMSTATE_STRUCT(tick_timer, Exynos4210MCTLT, 0, + vmstate_tick_timer, + struct tick_timer), + VMSTATE_PTIMER(ptimer_frc, Exynos4210MCTLT), + VMSTATE_STRUCT(reg, Exynos4210MCTLT, 0, + vmstate_lregs, + struct lregs), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_gregs = { + .name = "exynos4210.mct.lregs", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT64(cnt, struct gregs), + VMSTATE_UINT32(cnt_wstat, struct gregs), + VMSTATE_UINT32(tcon, struct gregs), + VMSTATE_UINT32(int_cstat, struct gregs), + VMSTATE_UINT32(int_enb, struct gregs), + VMSTATE_UINT32(wstat, struct gregs), + VMSTATE_UINT64_ARRAY(comp, struct gregs, MCT_GT_CMP_NUM), + VMSTATE_UINT32_ARRAY(comp_add_incr, struct gregs, + MCT_GT_CMP_NUM), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_exynos4210_mct_gt = { + .name = "exynos4210.mct.lt", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(reg, Exynos4210MCTGT, 0, vmstate_gregs, + struct gregs), + VMSTATE_UINT64(count, Exynos4210MCTGT), + VMSTATE_INT32(curr_comp, Exynos4210MCTGT), + VMSTATE_PTIMER(ptimer_frc, Exynos4210MCTGT), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_exynos4210_mct_state = { + .name = "exynos4210.mct", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(reg_mct_cfg, Exynos4210MCTState), + VMSTATE_STRUCT_ARRAY(l_timer, Exynos4210MCTState, 2, 0, + vmstate_exynos4210_mct_lt, Exynos4210MCTLT), + VMSTATE_STRUCT(g_timer, Exynos4210MCTState, 0, + vmstate_exynos4210_mct_gt, Exynos4210MCTGT), + VMSTATE_UINT32(freq, Exynos4210MCTState), + VMSTATE_END_OF_LIST() + } +}; + +static void exynos4210_mct_update_freq(Exynos4210MCTState *s); + +/* + * Set counter of FRC global timer. + */ +static void exynos4210_gfrc_set_count(Exynos4210MCTGT *s, uint64_t count) +{ + s->count = count; + DPRINTF("global timer frc set count 0x%llx\n", count); + ptimer_set_count(s->ptimer_frc, count); +} + +/* + * Get counter of FRC global timer. + */ +static uint64_t exynos4210_gfrc_get_count(Exynos4210MCTGT *s) +{ + uint64_t count = 0; + count = ptimer_get_count(s->ptimer_frc); + if (!count) { + /* Timer event was generated and s->reg.cnt holds adequate value */ + return s->reg.cnt; + } + count = s->count - count; + return s->reg.cnt + count; +} + +/* + * Stop global FRC timer + */ +static void exynos4210_gfrc_stop(Exynos4210MCTGT *s) +{ + DPRINTF("global timer frc stop\n"); + + ptimer_stop(s->ptimer_frc); +} + +/* + * Start global FRC timer + */ +static void exynos4210_gfrc_start(Exynos4210MCTGT *s) +{ + DPRINTF("global timer frc start\n"); + + ptimer_run(s->ptimer_frc, 1); +} + +/* + * Find next nearest Comparator. If current Comparator value equals to other + * Comparator value, skip them both + */ +static int32_t exynos4210_gcomp_find(Exynos4210MCTState *s) +{ + int res; + int i; + int enabled; + uint64_t min; + int min_comp_i; + uint64_t gfrc; + uint64_t distance; + uint64_t distance_min; + int comp_i; + + /* get gfrc count */ + gfrc = exynos4210_gfrc_get_count(&s->g_timer); + + min = UINT64_MAX; + distance_min = UINT64_MAX; + comp_i = MCT_GT_CMP_NUM; + min_comp_i = MCT_GT_CMP_NUM; + enabled = 0; + + /* lookup for nearest comparator */ + for (i = 0; i < MCT_GT_CMP_NUM; i++) { + + if (s->g_timer.reg.tcon & G_TCON_COMP_ENABLE(i)) { + + enabled = 1; + + if (s->g_timer.reg.comp[i] > gfrc) { + /* Comparator is upper then FRC */ + distance = s->g_timer.reg.comp[i] - gfrc; + + if (distance <= distance_min) { + distance_min = distance; + comp_i = i; + } + } else { + /* Comparator is below FRC, find the smallest */ + + if (s->g_timer.reg.comp[i] <= min) { + min = s->g_timer.reg.comp[i]; + min_comp_i = i; + } + } + } + } + + if (!enabled) { + /* All Comparators disabled */ + res = -1; + } else if (comp_i < MCT_GT_CMP_NUM) { + /* Found upper Comparator */ + res = comp_i; + } else { + /* All Comparators are below or equal to FRC */ + res = min_comp_i; + } + + DPRINTF("found comparator %d: comp 0x%llx distance 0x%llx, gfrc 0x%llx\n", + res, + s->g_timer.reg.comp[res], + distance_min, + gfrc); + + return res; +} + +/* + * Get distance to nearest Comparator + */ +static uint64_t exynos4210_gcomp_get_distance(Exynos4210MCTState *s, int32_t id) +{ + if (id == -1) { + /* no enabled Comparators, choose max distance */ + return MCT_GT_COUNTER_STEP; + } + if (s->g_timer.reg.comp[id] - s->g_timer.reg.cnt < MCT_GT_COUNTER_STEP) { + return s->g_timer.reg.comp[id] - s->g_timer.reg.cnt; + } else { + return MCT_GT_COUNTER_STEP; + } +} + +/* + * Restart global FRC timer + */ +static void exynos4210_gfrc_restart(Exynos4210MCTState *s) +{ + uint64_t distance; + + exynos4210_gfrc_stop(&s->g_timer); + + s->g_timer.curr_comp = exynos4210_gcomp_find(s); + + distance = exynos4210_gcomp_get_distance(s, s->g_timer.curr_comp); + + if (distance > MCT_GT_COUNTER_STEP || !distance) { + distance = MCT_GT_COUNTER_STEP; + } + + exynos4210_gfrc_set_count(&s->g_timer, distance); + exynos4210_gfrc_start(&s->g_timer); +} + +/* + * Raise global timer CMP IRQ + */ +static void exynos4210_gcomp_raise_irq(void *opaque, uint32_t id) +{ + Exynos4210MCTGT *s = opaque; + + /* If CSTAT is pending and IRQ is enabled */ + if ((s->reg.int_cstat & G_INT_CSTAT_COMP(id)) && + (s->reg.int_enb & G_INT_ENABLE(id))) { + DPRINTF("gcmp timer[%d] IRQ\n", id); + qemu_irq_raise(s->irq[id]); + } +} + +/* + * Lower global timer CMP IRQ + */ +static void exynos4210_gcomp_lower_irq(void *opaque, uint32_t id) +{ + Exynos4210MCTGT *s = opaque; + qemu_irq_lower(s->irq[id]); +} + +/* + * Global timer FRC event handler. + * Each event occurs when internal counter reaches counter + MCT_GT_COUNTER_STEP + * Every time we arm global FRC timer to count for MCT_GT_COUNTER_STEP value + */ +static void exynos4210_gfrc_event(void *opaque) +{ + Exynos4210MCTState *s = (Exynos4210MCTState *)opaque; + int i; + uint64_t distance; + + DPRINTF("\n"); + + s->g_timer.reg.cnt += s->g_timer.count; + + /* Process all comparators */ + for (i = 0; i < MCT_GT_CMP_NUM; i++) { + + if (s->g_timer.reg.cnt == s->g_timer.reg.comp[i]) { + /* reached nearest comparator */ + + s->g_timer.reg.int_cstat |= G_INT_CSTAT_COMP(i); + + /* Auto increment */ + if (s->g_timer.reg.tcon & G_TCON_AUTO_ICREMENT(i)) { + s->g_timer.reg.comp[i] += s->g_timer.reg.comp_add_incr[i]; + } + + /* IRQ */ + exynos4210_gcomp_raise_irq(&s->g_timer, i); + } + } + + /* Reload FRC to reach nearest comparator */ + s->g_timer.curr_comp = exynos4210_gcomp_find(s); + distance = exynos4210_gcomp_get_distance(s, s->g_timer.curr_comp); + if (distance > MCT_GT_COUNTER_STEP) { + distance = MCT_GT_COUNTER_STEP; + } + exynos4210_gfrc_set_count(&s->g_timer, distance); + + exynos4210_gfrc_start(&s->g_timer); + + return; +} + +/* + * Get counter of FRC local timer. + */ +static uint64_t exynos4210_lfrc_get_count(Exynos4210MCTLT *s) +{ + return ptimer_get_count(s->ptimer_frc); +} + +/* + * Set counter of FRC local timer. + */ +static void exynos4210_lfrc_update_count(Exynos4210MCTLT *s) +{ + if (!s->reg.cnt[L_REG_CNT_FRCCNTB]) { + ptimer_set_count(s->ptimer_frc, MCT_LT_COUNTER_STEP); + } else { + ptimer_set_count(s->ptimer_frc, s->reg.cnt[L_REG_CNT_FRCCNTB]); + } +} + +/* + * Start local FRC timer + */ +static void exynos4210_lfrc_start(Exynos4210MCTLT *s) +{ + ptimer_run(s->ptimer_frc, 1); +} + +/* + * Stop local FRC timer + */ +static void exynos4210_lfrc_stop(Exynos4210MCTLT *s) +{ + ptimer_stop(s->ptimer_frc); +} + +/* + * Local timer free running counter tick handler + */ +static void exynos4210_lfrc_event(void *opaque) +{ + Exynos4210MCTLT * s = (Exynos4210MCTLT *)opaque; + + /* local frc expired */ + + DPRINTF("\n"); + + s->reg.int_cstat |= L_INT_CSTAT_FRCCNT; + + /* update frc counter */ + exynos4210_lfrc_update_count(s); + + /* raise irq */ + if (s->reg.int_enb & L_INT_INTENB_FRCEIE) { + qemu_irq_raise(s->irq); + } + + /* we reached here, this means that timer is enabled */ + exynos4210_lfrc_start(s); +} + +static uint32_t exynos4210_ltick_int_get_cnto(struct tick_timer *s); +static uint32_t exynos4210_ltick_cnt_get_cnto(struct tick_timer *s); +static void exynos4210_ltick_recalc_count(struct tick_timer *s); + +/* + * Action on enabling local tick int timer + */ +static void exynos4210_ltick_int_start(struct tick_timer *s) +{ + if (!s->int_run) { + s->int_run = 1; + } +} + +/* + * Action on disabling local tick int timer + */ +static void exynos4210_ltick_int_stop(struct tick_timer *s) +{ + if (s->int_run) { + s->last_icnto = exynos4210_ltick_int_get_cnto(s); + s->int_run = 0; + } +} + +/* + * Get count for INT timer + */ +static uint32_t exynos4210_ltick_int_get_cnto(struct tick_timer *s) +{ + uint32_t icnto; + uint64_t remain; + uint64_t count; + uint64_t counted; + uint64_t cur_progress; + + count = ptimer_get_count(s->ptimer_tick); + if (count) { + /* timer is still counting, called not from event */ + counted = s->count - ptimer_get_count(s->ptimer_tick); + cur_progress = s->progress + counted; + } else { + /* timer expired earlier */ + cur_progress = s->progress; + } + + remain = s->distance - cur_progress; + + if (!s->int_run) { + /* INT is stopped. */ + icnto = s->last_icnto; + } else { + /* Both are counting */ + icnto = remain / s->tcntb; + } + + return icnto; +} + +/* + * Start local tick cnt timer. + */ +static void exynos4210_ltick_cnt_start(struct tick_timer *s) +{ + if (!s->cnt_run) { + + exynos4210_ltick_recalc_count(s); + ptimer_set_count(s->ptimer_tick, s->count); + ptimer_run(s->ptimer_tick, 1); + + s->cnt_run = 1; + } +} + +/* + * Stop local tick cnt timer. + */ +static void exynos4210_ltick_cnt_stop(struct tick_timer *s) +{ + if (s->cnt_run) { + + s->last_tcnto = exynos4210_ltick_cnt_get_cnto(s); + + if (s->int_run) { + exynos4210_ltick_int_stop(s); + } + + ptimer_stop(s->ptimer_tick); + + s->cnt_run = 0; + } +} + +/* + * Get counter for CNT timer + */ +static uint32_t exynos4210_ltick_cnt_get_cnto(struct tick_timer *s) +{ + uint32_t tcnto; + uint32_t icnto; + uint64_t remain; + uint64_t counted; + uint64_t count; + uint64_t cur_progress; + + count = ptimer_get_count(s->ptimer_tick); + if (count) { + /* timer is still counting, called not from event */ + counted = s->count - ptimer_get_count(s->ptimer_tick); + cur_progress = s->progress + counted; + } else { + /* timer expired earlier */ + cur_progress = s->progress; + } + + remain = s->distance - cur_progress; + + if (!s->cnt_run) { + /* Both are stopped. */ + tcnto = s->last_tcnto; + } else if (!s->int_run) { + /* INT counter is stopped, progress is by CNT timer */ + tcnto = remain % s->tcntb; + } else { + /* Both are counting */ + icnto = remain / s->tcntb; + if (icnto) { + tcnto = remain % (icnto * s->tcntb); + } else { + tcnto = remain % s->tcntb; + } + } + + return tcnto; +} + +/* + * Set new values of counters for CNT and INT timers + */ +static void exynos4210_ltick_set_cntb(struct tick_timer *s, uint32_t new_cnt, + uint32_t new_int) +{ + uint32_t cnt_stopped = 0; + uint32_t int_stopped = 0; + + if (s->cnt_run) { + exynos4210_ltick_cnt_stop(s); + cnt_stopped = 1; + } + + if (s->int_run) { + exynos4210_ltick_int_stop(s); + int_stopped = 1; + } + + s->tcntb = new_cnt + 1; + s->icntb = new_int + 1; + + if (cnt_stopped) { + exynos4210_ltick_cnt_start(s); + } + if (int_stopped) { + exynos4210_ltick_int_start(s); + } + +} + +/* + * Calculate new counter value for tick timer + */ +static void exynos4210_ltick_recalc_count(struct tick_timer *s) +{ + uint64_t to_count; + + if ((s->cnt_run && s->last_tcnto) || (s->int_run && s->last_icnto)) { + /* + * one or both timers run and not counted to the end; + * distance is not passed, recalculate with last_tcnto * last_icnto + */ + + if (s->last_tcnto) { + to_count = s->last_tcnto * s->last_icnto; + } else { + to_count = s->last_icnto; + } + } else { + /* distance is passed, recalculate with tcnto * icnto */ + if (s->icntb) { + s->distance = s->tcntb * s->icntb; + } else { + s->distance = s->tcntb; + } + + to_count = s->distance; + s->progress = 0; + } + + if (to_count > MCT_LT_COUNTER_STEP) { + /* count by step */ + s->count = MCT_LT_COUNTER_STEP; + } else { + s->count = to_count; + } +} + +/* + * Initialize tick_timer + */ +static void exynos4210_ltick_timer_init(struct tick_timer *s) +{ + exynos4210_ltick_int_stop(s); + exynos4210_ltick_cnt_stop(s); + + s->count = 0; + s->distance = 0; + s->progress = 0; + s->icntb = 0; + s->tcntb = 0; +} + +/* + * tick_timer event. + * Raises when abstract tick_timer expires. + */ +static void exynos4210_ltick_timer_event(struct tick_timer *s) +{ + s->progress += s->count; +} + +/* + * Local timer tick counter handler. + * Don't use reloaded timers. If timer counter = zero + * then handler called but after handler finished no + * timer reload occurs. + */ +static void exynos4210_ltick_event(void *opaque) +{ + Exynos4210MCTLT * s = (Exynos4210MCTLT *)opaque; + uint32_t tcnto; + uint32_t icnto; +#ifdef DEBUG_MCT + static uint64_t time1[2] = {0}; + static uint64_t time2[2] = {0}; +#endif + + /* Call tick_timer event handler, it will update it's tcntb and icntb */ + exynos4210_ltick_timer_event(&s->tick_timer); + + /* get tick_timer cnt */ + tcnto = exynos4210_ltick_cnt_get_cnto(&s->tick_timer); + + /* get tick_timer int */ + icnto = exynos4210_ltick_int_get_cnto(&s->tick_timer); + + /* raise IRQ if needed */ + if (!icnto && s->reg.tcon & L_TCON_INT_START) { + /* INT counter enabled and expired */ + + s->reg.int_cstat |= L_INT_CSTAT_INTCNT; + + /* raise interrupt if enabled */ + if (s->reg.int_enb & L_INT_INTENB_ICNTEIE) { +#ifdef DEBUG_MCT + time2[s->id] = qemu_get_clock_ns(vm_clock); + DPRINTF("local timer[%d] IRQ: %llx\n", s->id, + time2[s->id] - time1[s->id]); + time1[s->id] = time2[s->id]; +#endif + qemu_irq_raise(s->irq); + } + + /* reload ICNTB */ + if (s->reg.tcon & L_TCON_INTERVAL_MODE) { + exynos4210_ltick_set_cntb(&s->tick_timer, + s->reg.cnt[L_REG_CNT_TCNTB], + s->reg.cnt[L_REG_CNT_ICNTB]); + } + } else { + /* reload TCNTB */ + if (!tcnto) { + exynos4210_ltick_set_cntb(&s->tick_timer, + s->reg.cnt[L_REG_CNT_TCNTB], + icnto); + } + } + + /* start tick_timer cnt */ + exynos4210_ltick_cnt_start(&s->tick_timer); + + /* start tick_timer int */ + exynos4210_ltick_int_start(&s->tick_timer); +} + +/* update timer frequency */ +static void exynos4210_mct_update_freq(Exynos4210MCTState *s) +{ + uint32_t freq = s->freq; + s->freq = 24000000 / + ((MCT_CFG_GET_PRESCALER(s->reg_mct_cfg)+1) * + MCT_CFG_GET_DIVIDER(s->reg_mct_cfg)); + + if (freq != s->freq) { + DPRINTF("freq=%dHz\n", s->freq); + + /* global timer */ + ptimer_set_freq(s->g_timer.ptimer_frc, s->freq); + + /* local timer */ + ptimer_set_freq(s->l_timer[0].tick_timer.ptimer_tick, s->freq); + ptimer_set_freq(s->l_timer[0].ptimer_frc, s->freq); + ptimer_set_freq(s->l_timer[1].tick_timer.ptimer_tick, s->freq); + ptimer_set_freq(s->l_timer[1].ptimer_frc, s->freq); + } +} + +/* set defaul_timer values for all fields */ +static void exynos4210_mct_reset(DeviceState *d) +{ + Exynos4210MCTState *s = (Exynos4210MCTState *)d; + uint32_t i; + + s->reg_mct_cfg = 0; + + /* global timer */ + memset(&s->g_timer.reg, 0, sizeof(s->g_timer.reg)); + exynos4210_gfrc_stop(&s->g_timer); + + /* local timer */ + memset(s->l_timer[0].reg.cnt, 0, sizeof(s->l_timer[0].reg.cnt)); + memset(s->l_timer[1].reg.cnt, 0, sizeof(s->l_timer[1].reg.cnt)); + for (i = 0; i < 2; i++) { + s->l_timer[i].reg.int_cstat = 0; + s->l_timer[i].reg.int_enb = 0; + s->l_timer[i].reg.tcon = 0; + s->l_timer[i].reg.wstat = 0; + s->l_timer[i].tick_timer.count = 0; + s->l_timer[i].tick_timer.distance = 0; + s->l_timer[i].tick_timer.progress = 0; + ptimer_stop(s->l_timer[i].ptimer_frc); + + exynos4210_ltick_timer_init(&s->l_timer[i].tick_timer); + } + + exynos4210_mct_update_freq(s); + +} + +/* Multi Core Timer read */ +static uint64_t exynos4210_mct_read(void *opaque, target_phys_addr_t offset, + unsigned size) +{ + Exynos4210MCTState *s = (Exynos4210MCTState *)opaque; + int index; + int shift; + uint64_t count; + uint32_t value; + int lt_i; + + switch (offset) { + + case MCT_CFG: + value = s->reg_mct_cfg; + break; + + case G_CNT_L: case G_CNT_U: + shift = 8 * (offset & 0x4); + count = exynos4210_gfrc_get_count(&s->g_timer); + value = UINT32_MAX & (count >> shift); + DPRINTF("read FRC=0x%llx\n", count); + break; + + case G_CNT_WSTAT: + value = s->g_timer.reg.cnt_wstat; + break; + + case G_COMP_L(0): case G_COMP_L(1): case G_COMP_L(2): case G_COMP_L(3): + case G_COMP_U(0): case G_COMP_U(1): case G_COMP_U(2): case G_COMP_U(3): + index = GET_G_COMP_IDX(offset); + shift = 8 * (offset & 0x4); + value = UINT32_MAX & (s->g_timer.reg.comp[index] >> shift); + break; + + case G_TCON: + value = s->g_timer.reg.tcon; + break; + + case G_INT_CSTAT: + value = s->g_timer.reg.int_cstat; + break; + + case G_INT_ENB: + value = s->g_timer.reg.int_enb; + break; + break; + case G_WSTAT: + value = s->g_timer.reg.wstat; + break; + + case G_COMP0_ADD_INCR: case G_COMP1_ADD_INCR: + case G_COMP2_ADD_INCR: case G_COMP3_ADD_INCR: + value = s->g_timer.reg.comp_add_incr[GET_G_COMP_ADD_INCR_IDX(offset)]; + break; + + /* Local timers */ + case L0_TCNTB: case L0_ICNTB: case L0_FRCNTB: + case L1_TCNTB: case L1_ICNTB: case L1_FRCNTB: + lt_i = GET_L_TIMER_IDX(offset); + index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i); + value = s->l_timer[lt_i].reg.cnt[index]; + break; + + case L0_TCNTO: case L1_TCNTO: + lt_i = GET_L_TIMER_IDX(offset); + + value = exynos4210_ltick_cnt_get_cnto(&s->l_timer[lt_i].tick_timer); + DPRINTF("local timer[%d] read TCNTO %x\n", lt_i, value); + break; + + case L0_ICNTO: case L1_ICNTO: + lt_i = GET_L_TIMER_IDX(offset); + + value = exynos4210_ltick_int_get_cnto(&s->l_timer[lt_i].tick_timer); + DPRINTF("local timer[%d] read ICNTO %x\n", lt_i, value); + break; + + case L0_FRCNTO: case L1_FRCNTO: + lt_i = GET_L_TIMER_IDX(offset); + + value = exynos4210_lfrc_get_count(&s->l_timer[lt_i]); + + break; + + case L0_TCON: case L1_TCON: + lt_i = ((offset & 0xF00) - L0_TCNTB) / 0x100; + value = s->l_timer[lt_i].reg.tcon; + break; + + case L0_INT_CSTAT: case L1_INT_CSTAT: + lt_i = ((offset & 0xF00) - L0_TCNTB) / 0x100; + value = s->l_timer[lt_i].reg.int_cstat; + break; + + case L0_INT_ENB: case L1_INT_ENB: + lt_i = ((offset & 0xF00) - L0_TCNTB) / 0x100; + value = s->l_timer[lt_i].reg.int_enb; + break; + + case L0_WSTAT: case L1_WSTAT: + lt_i = ((offset & 0xF00) - L0_TCNTB) / 0x100; + value = s->l_timer[lt_i].reg.wstat; + break; + + default: + hw_error("exynos4210.mct: bad read offset " + TARGET_FMT_plx "\n", offset); + break; + } + return value; +} + +/* MCT write */ +static void exynos4210_mct_write(void *opaque, target_phys_addr_t offset, + uint64_t value, unsigned size) +{ + Exynos4210MCTState *s = (Exynos4210MCTState *)opaque; + int index; /* index in buffer which represents register set */ + int shift; + int lt_i; + uint64_t new_frc; + uint32_t i; + uint32_t old_val; +#ifdef DEBUG_MCT + static uint32_t icntb_max[2] = {0}; + static uint32_t icntb_min[2] = {UINT32_MAX, UINT32_MAX}; + static uint32_t tcntb_max[2] = {0}; + static uint32_t tcntb_min[2] = {UINT32_MAX, UINT32_MAX}; +#endif + + new_frc = s->g_timer.reg.cnt; + + switch (offset) { + + case MCT_CFG: + s->reg_mct_cfg = value; + exynos4210_mct_update_freq(s); + break; + + case G_CNT_L: + case G_CNT_U: + if (offset == G_CNT_L) { + + DPRINTF("global timer write to reg.cntl %llx\n", value); + + new_frc = (s->g_timer.reg.cnt & (uint64_t)UINT32_MAX << 32) + value; + s->g_timer.reg.cnt_wstat |= G_CNT_WSTAT_L; + } + if (offset == G_CNT_U) { + + DPRINTF("global timer write to reg.cntu %llx\n", value); + + new_frc = (s->g_timer.reg.cnt & UINT32_MAX) + + ((uint64_t)value << 32); + s->g_timer.reg.cnt_wstat |= G_CNT_WSTAT_U; + } + + s->g_timer.reg.cnt = new_frc; + exynos4210_gfrc_restart(s); + break; + + case G_CNT_WSTAT: + s->g_timer.reg.cnt_wstat &= ~(value); + break; + + case G_COMP_L(0): case G_COMP_L(1): case G_COMP_L(2): case G_COMP_L(3): + case G_COMP_U(0): case G_COMP_U(1): case G_COMP_U(2): case G_COMP_U(3): + index = GET_G_COMP_IDX(offset); + shift = 8 * (offset & 0x4); + s->g_timer.reg.comp[index] = + (s->g_timer.reg.comp[index] & + (((uint64_t)UINT32_MAX << 32) >> shift)) + + (value << shift); + + DPRINTF("comparator %d write 0x%llx val << %d\n", index, value, shift); + + if (offset&0x4) { + s->g_timer.reg.wstat |= G_WSTAT_COMP_U(index); + } else { + s->g_timer.reg.wstat |= G_WSTAT_COMP_L(index); + } + + exynos4210_gfrc_restart(s); + break; + + case G_TCON: + old_val = s->g_timer.reg.tcon; + s->g_timer.reg.tcon = value; + s->g_timer.reg.wstat |= G_WSTAT_TCON_WRITE; + + DPRINTF("global timer write to reg.g_tcon %llx\n", value); + + /* Start FRC if transition from disabled to enabled */ + if ((value & G_TCON_TIMER_ENABLE) > (old_val & + G_TCON_TIMER_ENABLE)) { + exynos4210_gfrc_start(&s->g_timer); + } + if ((value & G_TCON_TIMER_ENABLE) < (old_val & + G_TCON_TIMER_ENABLE)) { + exynos4210_gfrc_stop(&s->g_timer); + } + + /* Start CMP if transition from disabled to enabled */ + for (i = 0; i < MCT_GT_CMP_NUM; i++) { + if ((value & G_TCON_COMP_ENABLE(i)) != (old_val & + G_TCON_COMP_ENABLE(i))) { + exynos4210_gfrc_restart(s); + } + } + break; + + case G_INT_CSTAT: + s->g_timer.reg.int_cstat &= ~(value); + for (i = 0; i < MCT_GT_CMP_NUM; i++) { + if (value & G_INT_CSTAT_COMP(i)) { + exynos4210_gcomp_lower_irq(&s->g_timer, i); + } + } + break; + + case G_INT_ENB: + + /* Raise IRQ if transition from disabled to enabled and CSTAT pending */ + for (i = 0; i < MCT_GT_CMP_NUM; i++) { + if ((value & G_INT_ENABLE(i)) > (s->g_timer.reg.tcon & + G_INT_ENABLE(i))) { + if (s->g_timer.reg.int_cstat & G_INT_CSTAT_COMP(i)) { + exynos4210_gcomp_raise_irq(&s->g_timer, i); + } + } + + if ((value & G_INT_ENABLE(i)) < (s->g_timer.reg.tcon & + G_INT_ENABLE(i))) { + exynos4210_gcomp_lower_irq(&s->g_timer, i); + } + } + + DPRINTF("global timer INT enable %llx\n", value); + s->g_timer.reg.int_enb = value; + break; + + case G_WSTAT: + s->g_timer.reg.wstat &= ~(value); + break; + + case G_COMP0_ADD_INCR: case G_COMP1_ADD_INCR: + case G_COMP2_ADD_INCR: case G_COMP3_ADD_INCR: + index = GET_G_COMP_ADD_INCR_IDX(offset); + s->g_timer.reg.comp_add_incr[index] = value; + s->g_timer.reg.wstat |= G_WSTAT_COMP_ADDINCR(index); + break; + + /* Local timers */ + case L0_TCON: case L1_TCON: + lt_i = GET_L_TIMER_IDX(offset); + old_val = s->l_timer[lt_i].reg.tcon; + + s->l_timer[lt_i].reg.wstat |= L_WSTAT_TCON_WRITE; + s->l_timer[lt_i].reg.tcon = value; + + /* Stop local CNT */ + if ((value & L_TCON_TICK_START) < + (old_val & L_TCON_TICK_START)) { + DPRINTF("local timer[%d] stop cnt\n", lt_i); + exynos4210_ltick_cnt_stop(&s->l_timer[lt_i].tick_timer); + } + + /* Stop local INT */ + if ((value & L_TCON_INT_START) < + (old_val & L_TCON_INT_START)) { + DPRINTF("local timer[%d] stop int\n", lt_i); + exynos4210_ltick_int_stop(&s->l_timer[lt_i].tick_timer); + } + + /* Start local CNT */ + if ((value & L_TCON_TICK_START) > + (old_val & L_TCON_TICK_START)) { + DPRINTF("local timer[%d] start cnt\n", lt_i); + exynos4210_ltick_cnt_start(&s->l_timer[lt_i].tick_timer); + } + + /* Start local INT */ + if ((value & L_TCON_INT_START) > + (old_val & L_TCON_INT_START)) { + DPRINTF("local timer[%d] start int\n", lt_i); + exynos4210_ltick_int_start(&s->l_timer[lt_i].tick_timer); + } + + /* Start or Stop local FRC if TCON changed */ + if ((value & L_TCON_FRC_START) > + (s->l_timer[lt_i].reg.tcon & L_TCON_FRC_START)) { + DPRINTF("local timer[%d] start frc\n", lt_i); + exynos4210_lfrc_start(&s->l_timer[lt_i]); + } + if ((value & L_TCON_FRC_START) < + (s->l_timer[lt_i].reg.tcon & L_TCON_FRC_START)) { + DPRINTF("local timer[%d] stop frc\n", lt_i); + exynos4210_lfrc_stop(&s->l_timer[lt_i]); + } + break; + + case L0_TCNTB: case L1_TCNTB: + + lt_i = GET_L_TIMER_IDX(offset); + index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i); + + /* + * TCNTB is updated to internal register only after CNT expired. + * Due to this we should reload timer to nearest moment when CNT is + * expired and then in event handler update tcntb to new TCNTB value. + */ + exynos4210_ltick_set_cntb(&s->l_timer[lt_i].tick_timer, value, + s->l_timer[lt_i].tick_timer.icntb); + + s->l_timer[lt_i].reg.wstat |= L_WSTAT_TCNTB_WRITE; + s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB] = value; + +#ifdef DEBUG_MCT + if (tcntb_min[lt_i] > value) { + tcntb_min[lt_i] = value; + } + if (tcntb_max[lt_i] < value) { + tcntb_max[lt_i] = value; + } + DPRINTF("local timer[%d] TCNTB write %llx; max=%x, min=%x\n", + lt_i, value, tcntb_max[lt_i], tcntb_min[lt_i]); +#endif + break; + + case L0_ICNTB: case L1_ICNTB: + + lt_i = GET_L_TIMER_IDX(offset); + index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i); + + s->l_timer[lt_i].reg.wstat |= L_WSTAT_ICNTB_WRITE; + s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] = value & + ~L_ICNTB_MANUAL_UPDATE; + + /* + * We need to avoid too small values for TCNTB*ICNTB. If not, IRQ event + * could raise too fast disallowing QEMU to execute target code. + */ + if (s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] * + s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB] < MCT_LT_CNT_LOW_LIMIT) { + if (!s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB]) { + s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] = + MCT_LT_CNT_LOW_LIMIT; + } else { + s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] = + MCT_LT_CNT_LOW_LIMIT / + s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB]; + } + } + + if (value & L_ICNTB_MANUAL_UPDATE) { + exynos4210_ltick_set_cntb(&s->l_timer[lt_i].tick_timer, + s->l_timer[lt_i].tick_timer.tcntb, + s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB]); + } + +#ifdef DEBUG_MCT + if (icntb_min[lt_i] > value) { + icntb_min[lt_i] = value; + } + if (icntb_max[lt_i] < value) { + icntb_max[lt_i] = value; + } +DPRINTF("local timer[%d] ICNTB write %llx; max=%x, min=%x\n\n", + lt_i, value, icntb_max[lt_i], icntb_min[lt_i]); +#endif +break; + + case L0_FRCNTB: case L1_FRCNTB: + + lt_i = GET_L_TIMER_IDX(offset); + index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i); + + DPRINTF("local timer[%d] FRCNTB write %llx\n", lt_i, value); + + s->l_timer[lt_i].reg.wstat |= L_WSTAT_FRCCNTB_WRITE; + s->l_timer[lt_i].reg.cnt[L_REG_CNT_FRCCNTB] = value; + + break; + + case L0_TCNTO: case L1_TCNTO: + case L0_ICNTO: case L1_ICNTO: + case L0_FRCNTO: case L1_FRCNTO: + fprintf(stderr, "\n[exynos4210.mct: write to RO register " + TARGET_FMT_plx "]\n\n", offset); + break; + + case L0_INT_CSTAT: case L1_INT_CSTAT: + lt_i = GET_L_TIMER_IDX(offset); + + DPRINTF("local timer[%d] CSTAT write %llx\n", lt_i, value); + + s->l_timer[lt_i].reg.int_cstat &= ~value; + if (!s->l_timer[lt_i].reg.int_cstat) { + qemu_irq_lower(s->l_timer[lt_i].irq); + } + break; + + case L0_INT_ENB: case L1_INT_ENB: + lt_i = GET_L_TIMER_IDX(offset); + old_val = s->l_timer[lt_i].reg.int_enb; + + /* Raise Local timer IRQ if cstat is pending */ + if ((value & L_INT_INTENB_ICNTEIE) > (old_val & L_INT_INTENB_ICNTEIE)) { + if (s->l_timer[lt_i].reg.int_cstat & L_INT_CSTAT_INTCNT) { + qemu_irq_raise(s->l_timer[lt_i].irq); + } + } + + s->l_timer[lt_i].reg.int_enb = value; + + break; + + case L0_WSTAT: case L1_WSTAT: + lt_i = GET_L_TIMER_IDX(offset); + + s->l_timer[lt_i].reg.wstat &= ~value; + break; + + default: + hw_error("exynos4210.mct: bad write offset " + TARGET_FMT_plx "\n", offset); + break; + } +} + +static const MemoryRegionOps exynos4210_mct_ops = { + .read = exynos4210_mct_read, + .write = exynos4210_mct_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +/* MCT init */ +static int exynos4210_mct_init(SysBusDevice *dev) +{ + int i; + Exynos4210MCTState *s = FROM_SYSBUS(Exynos4210MCTState, dev); + QEMUBH *bh[2]; + + /* Global timer */ + bh[0] = qemu_bh_new(exynos4210_gfrc_event, s); + s->g_timer.ptimer_frc = ptimer_init(bh[0]); + memset(&s->g_timer.reg, 0, sizeof(struct gregs)); + + /* Local timers */ + for (i = 0; i < 2; i++) { + bh[0] = qemu_bh_new(exynos4210_ltick_event, &s->l_timer[i]); + bh[1] = qemu_bh_new(exynos4210_lfrc_event, &s->l_timer[i]); + s->l_timer[i].tick_timer.ptimer_tick = ptimer_init(bh[0]); + s->l_timer[i].ptimer_frc = ptimer_init(bh[1]); + s->l_timer[i].id = i; + } + + /* IRQs */ + for (i = 0; i < MCT_GT_CMP_NUM; i++) { + sysbus_init_irq(dev, &s->g_timer.irq[i]); + } + for (i = 0; i < 2; i++) { + sysbus_init_irq(dev, &s->l_timer[i].irq); + } + + memory_region_init_io(&s->iomem, &exynos4210_mct_ops, s, "exynos4210-mct", + MCT_SFR_SIZE); + sysbus_init_mmio(dev, &s->iomem); + + return 0; +} + +static void exynos4210_mct_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = exynos4210_mct_init; + dc->reset = exynos4210_mct_reset; + dc->vmsd = &vmstate_exynos4210_mct_state; +} + +static TypeInfo exynos4210_mct_info = { + .name = "exynos4210.mct", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Exynos4210MCTState), + .class_init = exynos4210_mct_class_init, +}; + +static void exynos4210_mct_register_types(void) +{ + type_register_static(&exynos4210_mct_info); +} + +type_init(exynos4210_mct_register_types) -- cgit v1.1 From 1248f8d4cbc3cd7b7956510f9d72188b5cd37287 Mon Sep 17 00:00:00 2001 From: Evgeny Voevodin Date: Thu, 16 Feb 2012 09:56:06 +0000 Subject: hw/lan9118: Add basic 16-bit mode support. Signed-off-by: Evgeny Voevodin Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- hw/lan9118.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 118 insertions(+), 6 deletions(-) (limited to 'hw') diff --git a/hw/lan9118.c b/hw/lan9118.c index aeb0c39..7b4fe87 100644 --- a/hw/lan9118.c +++ b/hw/lan9118.c @@ -235,11 +235,21 @@ typedef struct { int32_t rxp_offset; int32_t rxp_size; int32_t rxp_pad; + + uint32_t write_word_prev_offset; + uint32_t write_word_n; + uint16_t write_word_l; + uint16_t write_word_h; + uint32_t read_word_prev_offset; + uint32_t read_word_n; + uint32_t read_long; + + uint32_t mode_16bit; } lan9118_state; static const VMStateDescription vmstate_lan9118 = { .name = "lan9118", - .version_id = 1, + .version_id = 2, .minimum_version_id = 1, .fields = (VMStateField[]) { VMSTATE_PTIMER(timer, lan9118_state), @@ -294,6 +304,14 @@ static const VMStateDescription vmstate_lan9118 = { VMSTATE_INT32(rxp_offset, lan9118_state), VMSTATE_INT32(rxp_size, lan9118_state), VMSTATE_INT32(rxp_pad, lan9118_state), + VMSTATE_UINT32_V(write_word_prev_offset, lan9118_state, 2), + VMSTATE_UINT32_V(write_word_n, lan9118_state, 2), + VMSTATE_UINT16_V(write_word_l, lan9118_state, 2), + VMSTATE_UINT16_V(write_word_h, lan9118_state, 2), + VMSTATE_UINT32_V(read_word_prev_offset, lan9118_state, 2), + VMSTATE_UINT32_V(read_word_n, lan9118_state, 2), + VMSTATE_UINT32_V(read_long, lan9118_state, 2), + VMSTATE_UINT32_V(mode_16bit, lan9118_state, 2), VMSTATE_END_OF_LIST() } }; @@ -390,7 +408,7 @@ static void lan9118_reset(DeviceState *d) s->fifo_int = 0x48000000; s->rx_cfg = 0; s->tx_cfg = 0; - s->hw_cfg = 0x00050000; + s->hw_cfg = s->mode_16bit ? 0x00050000 : 0x00050004; s->pmt_ctrl &= 0x45; s->gpio_cfg = 0; s->txp->fifo_used = 0; @@ -429,6 +447,9 @@ static void lan9118_reset(DeviceState *d) s->mac_mii_data = 0; s->mac_flow = 0; + s->read_word_n = 0; + s->write_word_n = 0; + phy_reset(s); s->eeprom_writable = 0; @@ -984,7 +1005,7 @@ static void lan9118_writel(void *opaque, target_phys_addr_t offset, { lan9118_state *s = (lan9118_state *)opaque; offset &= 0xff; - + //DPRINTF("Write reg 0x%02x = 0x%08x\n", (int)offset, val); if (offset >= 0x20 && offset < 0x40) { /* TX FIFO */ @@ -1034,7 +1055,7 @@ static void lan9118_writel(void *opaque, target_phys_addr_t offset, /* SRST */ lan9118_reset(&s->busdev.qdev); } else { - s->hw_cfg = val & 0x003f300; + s->hw_cfg = (val & 0x003f300) | (s->hw_cfg & 0x4); } break; case CSR_RX_DP_CTRL: @@ -1113,6 +1134,46 @@ static void lan9118_writel(void *opaque, target_phys_addr_t offset, lan9118_update(s); } +static void lan9118_writew(void *opaque, target_phys_addr_t offset, + uint32_t val) +{ + lan9118_state *s = (lan9118_state *)opaque; + offset &= 0xff; + + if (s->write_word_prev_offset != (offset & ~0x3)) { + /* New offset, reset word counter */ + s->write_word_n = 0; + s->write_word_prev_offset = offset & ~0x3; + } + + if (offset & 0x2) { + s->write_word_h = val; + } else { + s->write_word_l = val; + } + + //DPRINTF("Writew reg 0x%02x = 0x%08x\n", (int)offset, val); + s->write_word_n++; + if (s->write_word_n == 2) { + s->write_word_n = 0; + lan9118_writel(s, offset & ~3, s->write_word_l + + (s->write_word_h << 16), 4); + } +} + +static void lan9118_16bit_mode_write(void *opaque, target_phys_addr_t offset, + uint64_t val, unsigned size) +{ + switch (size) { + case 2: + return lan9118_writew(opaque, offset, (uint32_t)val); + case 4: + return lan9118_writel(opaque, offset, val, size); + } + + hw_error("lan9118_write: Bad size 0x%x\n", size); +} + static uint64_t lan9118_readl(void *opaque, target_phys_addr_t offset, unsigned size) { @@ -1149,7 +1210,7 @@ static uint64_t lan9118_readl(void *opaque, target_phys_addr_t offset, case CSR_TX_CFG: return s->tx_cfg; case CSR_HW_CFG: - return s->hw_cfg | 0x4; + return s->hw_cfg; case CSR_RX_DP_CTRL: return 0; case CSR_RX_FIFO_INF: @@ -1187,12 +1248,60 @@ static uint64_t lan9118_readl(void *opaque, target_phys_addr_t offset, return 0; } +static uint32_t lan9118_readw(void *opaque, target_phys_addr_t offset) +{ + lan9118_state *s = (lan9118_state *)opaque; + uint32_t val; + + if (s->read_word_prev_offset != (offset & ~0x3)) { + /* New offset, reset word counter */ + s->read_word_n = 0; + s->read_word_prev_offset = offset & ~0x3; + } + + s->read_word_n++; + if (s->read_word_n == 1) { + s->read_long = lan9118_readl(s, offset & ~3, 4); + } else { + s->read_word_n = 0; + } + + if (offset & 2) { + val = s->read_long >> 16; + } else { + val = s->read_long & 0xFFFF; + } + + //DPRINTF("Readw reg 0x%02x, val 0x%x\n", (int)offset, val); + return val; +} + +static uint64_t lan9118_16bit_mode_read(void *opaque, target_phys_addr_t offset, + unsigned size) +{ + switch (size) { + case 2: + return lan9118_readw(opaque, offset); + case 4: + return lan9118_readl(opaque, offset, size); + } + + hw_error("lan9118_read: Bad size 0x%x\n", size); + return 0; +} + static const MemoryRegionOps lan9118_mem_ops = { .read = lan9118_readl, .write = lan9118_writel, .endianness = DEVICE_NATIVE_ENDIAN, }; +static const MemoryRegionOps lan9118_16bit_mem_ops = { + .read = lan9118_16bit_mode_read, + .write = lan9118_16bit_mode_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + static void lan9118_cleanup(VLANClientState *nc) { lan9118_state *s = DO_UPCAST(NICState, nc, nc)->opaque; @@ -1214,8 +1323,10 @@ static int lan9118_init1(SysBusDevice *dev) lan9118_state *s = FROM_SYSBUS(lan9118_state, dev); QEMUBH *bh; int i; + const MemoryRegionOps *mem_ops = + s->mode_16bit ? &lan9118_16bit_mem_ops : &lan9118_mem_ops; - memory_region_init_io(&s->mmio, &lan9118_mem_ops, s, "lan9118-mmio", 0x100); + memory_region_init_io(&s->mmio, mem_ops, s, "lan9118-mmio", 0x100); sysbus_init_mmio(dev, &s->mmio); sysbus_init_irq(dev, &s->irq); qemu_macaddr_default_if_unset(&s->conf.macaddr); @@ -1240,6 +1351,7 @@ static int lan9118_init1(SysBusDevice *dev) static Property lan9118_properties[] = { DEFINE_NIC_PROPERTIES(lan9118_state, conf), + DEFINE_PROP_UINT32("mode_16bit", lan9118_state, mode_16bit, 0), DEFINE_PROP_END_OF_LIST(), }; -- cgit v1.1 From 2c2c6496f83b1746a118dbcb2a0e4f8c0305c2a8 Mon Sep 17 00:00:00 2001 From: Evgeny Voevodin Date: Thu, 16 Feb 2012 09:56:06 +0000 Subject: hw/exynos4210.c: Add LAN support for SMDKC210. SMDKC210 uses lan9215 chip, but lan9118 in 16-bit mode seems to be enough. Signed-off-by: Evgeny Voevodin Reviewed-by: Peter Maydell Signed-off-by: Peter Maydell --- hw/exynos4_boards.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) (limited to 'hw') diff --git a/hw/exynos4_boards.c b/hw/exynos4_boards.c index 767dc45..329efbe 100644 --- a/hw/exynos4_boards.c +++ b/hw/exynos4_boards.c @@ -23,6 +23,7 @@ #include "sysemu.h" #include "sysbus.h" +#include "net.h" #include "arm-misc.h" #include "exec-memory.h" #include "exynos4210.h" @@ -42,6 +43,8 @@ #define PRINT_DEBUG(fmt, args...) do {} while (0) #endif +#define SMDK_LAN9118_BASE_ADDR 0x05000000 + typedef enum Exynos4BoardType { EXYNOS4_BOARD_NURI, EXYNOS4_BOARD_SMDKC210, @@ -71,6 +74,24 @@ static struct arm_boot_info exynos4_board_binfo = { static QEMUMachine exynos4_machines[EXYNOS4_NUM_OF_BOARDS]; +static void lan9215_init(uint32_t base, qemu_irq irq) +{ + DeviceState *dev; + SysBusDevice *s; + + /* This should be a 9215 but the 9118 is close enough */ + if (nd_table[0].vlan) { + qemu_check_nic_model(&nd_table[0], "lan9118"); + dev = qdev_create(NULL, "lan9118"); + qdev_set_nic_properties(dev, &nd_table[0]); + qdev_prop_set_uint32(dev, "mode_16bit", 1); + qdev_init_nofail(dev); + s = sysbus_from_qdev(dev); + sysbus_mmio_map(s, 0, base); + sysbus_connect_irq(s, 0, irq); + } +} + static Exynos4210State *exynos4_boards_init_common( const char *kernel_filename, const char *kernel_cmdline, @@ -123,9 +144,11 @@ static void smdkc210_init(ram_addr_t ram_size, const char *kernel_filename, const char *kernel_cmdline, const char *initrd_filename, const char *cpu_model) { - exynos4_boards_init_common(kernel_filename, kernel_cmdline, - initrd_filename, EXYNOS4_BOARD_SMDKC210); + Exynos4210State *s = exynos4_boards_init_common(kernel_filename, + kernel_cmdline, initrd_filename, EXYNOS4_BOARD_SMDKC210); + lan9215_init(SMDK_LAN9118_BASE_ADDR, + qemu_irq_invert(s->irq_table[exynos4210_get_irq(37, 1)])); arm_load_kernel(first_cpu, &exynos4_board_binfo); } -- cgit v1.1 From 30628cb12de2e35d87cb04194113341b6a8922b2 Mon Sep 17 00:00:00 2001 From: Mitsyanko Igor Date: Thu, 16 Feb 2012 09:56:06 +0000 Subject: Exynos4210: added display controller implementation Exynos4210 display controller (FIMD) has 5 hardware windows with alpha and chroma key blending functions. Signed-off-by: Mitsyanko Igor Signed-off-by: Evgeny Voevodin Signed-off-by: Peter Maydell --- hw/exynos4210.c | 10 + hw/exynos4210_fimd.c | 1928 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1938 insertions(+) create mode 100644 hw/exynos4210_fimd.c (limited to 'hw') diff --git a/hw/exynos4210.c b/hw/exynos4210.c index 558f669..f904370 100644 --- a/hw/exynos4210.c +++ b/hw/exynos4210.c @@ -58,6 +58,9 @@ /* PMU SFR base address */ #define EXYNOS4210_PMU_BASE_ADDR 0x10020000 +/* Display controllers (FIMD) */ +#define EXYNOS4210_FIMD0_BASE_ADDR 0x11C00000 + static uint8_t chipid_and_omr[] = { 0x11, 0x02, 0x21, 0x43, 0x09, 0x00, 0x00, 0x00 }; @@ -256,5 +259,12 @@ Exynos4210State *exynos4210_init(MemoryRegion *system_mem, EXYNOS4210_UART3_FIFO_SIZE, 3, NULL, s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 3)]); + /*** Display controller (FIMD) ***/ + sysbus_create_varargs("exynos4210.fimd", EXYNOS4210_FIMD0_BASE_ADDR, + s->irq_table[exynos4210_get_irq(11, 0)], + s->irq_table[exynos4210_get_irq(11, 1)], + s->irq_table[exynos4210_get_irq(11, 2)], + NULL); + return s; } diff --git a/hw/exynos4210_fimd.c b/hw/exynos4210_fimd.c new file mode 100644 index 0000000..3313f00 --- /dev/null +++ b/hw/exynos4210_fimd.c @@ -0,0 +1,1928 @@ +/* + * Samsung exynos4210 Display Controller (FIMD) + * + * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. + * All rights reserved. + * Based on LCD controller for Samsung S5PC1xx-based board emulation + * by Kirill Batuzov + * + * Contributed by Mitsyanko Igor + * + * 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 . + */ + +#include "qemu-common.h" +#include "cpu-all.h" +#include "sysbus.h" +#include "console.h" +#include "pixel_ops.h" +#include "bswap.h" + +/* Debug messages configuration */ +#define EXYNOS4210_FIMD_DEBUG 0 +#define EXYNOS4210_FIMD_MODE_TRACE 0 + +#if EXYNOS4210_FIMD_DEBUG == 0 + #define DPRINT_L1(fmt, args...) do { } while (0) + #define DPRINT_L2(fmt, args...) do { } while (0) + #define DPRINT_ERROR(fmt, args...) do { } while (0) +#elif EXYNOS4210_FIMD_DEBUG == 1 + #define DPRINT_L1(fmt, args...) \ + do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0) + #define DPRINT_L2(fmt, args...) do { } while (0) + #define DPRINT_ERROR(fmt, args...) \ + do {fprintf(stderr, "QEMU FIMD ERROR: "fmt, ## args); } while (0) +#else + #define DPRINT_L1(fmt, args...) \ + do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0) + #define DPRINT_L2(fmt, args...) \ + do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0) + #define DPRINT_ERROR(fmt, args...) \ + do {fprintf(stderr, "QEMU FIMD ERROR: "fmt, ## args); } while (0) +#endif + +#if EXYNOS4210_FIMD_MODE_TRACE == 0 + #define DPRINT_TRACE(fmt, args...) do { } while (0) +#else + #define DPRINT_TRACE(fmt, args...) \ + do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0) +#endif + +#define NUM_OF_WINDOWS 5 +#define FIMD_REGS_SIZE 0x4114 + +/* Video main control registers */ +#define FIMD_VIDCON0 0x0000 +#define FIMD_VIDCON1 0x0004 +#define FIMD_VIDCON2 0x0008 +#define FIMD_VIDCON3 0x000C +#define FIMD_VIDCON0_ENVID_F (1 << 0) +#define FIMD_VIDCON0_ENVID (1 << 1) +#define FIMD_VIDCON0_ENVID_MASK ((1 << 0) | (1 << 1)) +#define FIMD_VIDCON1_ROMASK 0x07FFE000 + +/* Video time control registers */ +#define FIMD_VIDTCON_START 0x10 +#define FIMD_VIDTCON_END 0x1C +#define FIMD_VIDTCON2_SIZE_MASK 0x07FF +#define FIMD_VIDTCON2_HOR_SHIFT 0 +#define FIMD_VIDTCON2_VER_SHIFT 11 + +/* Window control registers */ +#define FIMD_WINCON_START 0x0020 +#define FIMD_WINCON_END 0x0030 +#define FIMD_WINCON_ROMASK 0x82200000 +#define FIMD_WINCON_ENWIN (1 << 0) +#define FIMD_WINCON_BLD_PIX (1 << 6) +#define FIMD_WINCON_ALPHA_MUL (1 << 7) +#define FIMD_WINCON_ALPHA_SEL (1 << 1) +#define FIMD_WINCON_SWAP 0x078000 +#define FIMD_WINCON_SWAP_SHIFT 15 +#define FIMD_WINCON_SWAP_WORD 0x1 +#define FIMD_WINCON_SWAP_HWORD 0x2 +#define FIMD_WINCON_SWAP_BYTE 0x4 +#define FIMD_WINCON_SWAP_BITS 0x8 +#define FIMD_WINCON_BUFSTAT_L (1 << 21) +#define FIMD_WINCON_BUFSTAT_H (1 << 31) +#define FIMD_WINCON_BUFSTATUS ((1 << 21) | (1 << 31)) +#define FIMD_WINCON_BUF0_STAT ((0 << 21) | (0 << 31)) +#define FIMD_WINCON_BUF1_STAT ((1 << 21) | (0 << 31)) +#define FIMD_WINCON_BUF2_STAT ((0 << 21) | (1 << 31)) +#define FIMD_WINCON_BUFSELECT ((1 << 20) | (1 << 30)) +#define FIMD_WINCON_BUF0_SEL ((0 << 20) | (0 << 30)) +#define FIMD_WINCON_BUF1_SEL ((1 << 20) | (0 << 30)) +#define FIMD_WINCON_BUF2_SEL ((0 << 20) | (1 << 30)) +#define FIMD_WINCON_BUFMODE (1 << 14) +#define IS_PALETTIZED_MODE(w) (w->wincon & 0xC) +#define PAL_MODE_WITH_ALPHA(x) ((x) == 7) +#define WIN_BPP_MODE(w) ((w->wincon >> 2) & 0xF) +#define WIN_BPP_MODE_WITH_ALPHA(w) \ + (WIN_BPP_MODE(w) == 0xD || WIN_BPP_MODE(w) == 0xE) + +/* Shadow control register */ +#define FIMD_SHADOWCON 0x0034 +#define FIMD_WINDOW_PROTECTED(s, w) ((s) & (1 << (10 + (w)))) +/* Channel mapping control register */ +#define FIMD_WINCHMAP 0x003C + +/* Window position control registers */ +#define FIMD_VIDOSD_START 0x0040 +#define FIMD_VIDOSD_END 0x0088 +#define FIMD_VIDOSD_COORD_MASK 0x07FF +#define FIMD_VIDOSD_HOR_SHIFT 11 +#define FIMD_VIDOSD_VER_SHIFT 0 +#define FIMD_VIDOSD_ALPHA_AEN0 0xFFF000 +#define FIMD_VIDOSD_AEN0_SHIFT 12 +#define FIMD_VIDOSD_ALPHA_AEN1 0x000FFF + +/* Frame buffer address registers */ +#define FIMD_VIDWADD0_START 0x00A0 +#define FIMD_VIDWADD0_END 0x00C4 +#define FIMD_VIDWADD0_END 0x00C4 +#define FIMD_VIDWADD1_START 0x00D0 +#define FIMD_VIDWADD1_END 0x00F4 +#define FIMD_VIDWADD2_START 0x0100 +#define FIMD_VIDWADD2_END 0x0110 +#define FIMD_VIDWADD2_PAGEWIDTH 0x1FFF +#define FIMD_VIDWADD2_OFFSIZE 0x1FFF +#define FIMD_VIDWADD2_OFFSIZE_SHIFT 13 +#define FIMD_VIDW0ADD0_B2 0x20A0 +#define FIMD_VIDW4ADD0_B2 0x20C0 + +/* Video interrupt control registers */ +#define FIMD_VIDINTCON0 0x130 +#define FIMD_VIDINTCON1 0x134 + +/* Window color key registers */ +#define FIMD_WKEYCON_START 0x140 +#define FIMD_WKEYCON_END 0x15C +#define FIMD_WKEYCON0_COMPKEY 0x00FFFFFF +#define FIMD_WKEYCON0_CTL_SHIFT 24 +#define FIMD_WKEYCON0_DIRCON (1 << 24) +#define FIMD_WKEYCON0_KEYEN (1 << 25) +#define FIMD_WKEYCON0_KEYBLEN (1 << 26) +/* Window color key alpha control register */ +#define FIMD_WKEYALPHA_START 0x160 +#define FIMD_WKEYALPHA_END 0x16C + +/* Dithering control register */ +#define FIMD_DITHMODE 0x170 + +/* Window alpha control registers */ +#define FIMD_VIDALPHA_ALPHA_LOWER 0x000F0F0F +#define FIMD_VIDALPHA_ALPHA_UPPER 0x00F0F0F0 +#define FIMD_VIDWALPHA_START 0x21C +#define FIMD_VIDWALPHA_END 0x240 + +/* Window color map registers */ +#define FIMD_WINMAP_START 0x180 +#define FIMD_WINMAP_END 0x190 +#define FIMD_WINMAP_EN (1 << 24) +#define FIMD_WINMAP_COLOR_MASK 0x00FFFFFF + +/* Window palette control registers */ +#define FIMD_WPALCON_HIGH 0x019C +#define FIMD_WPALCON_LOW 0x01A0 +#define FIMD_WPALCON_UPDATEEN (1 << 9) +#define FIMD_WPAL_W0PAL_L 0x07 +#define FIMD_WPAL_W0PAL_L_SHT 0 +#define FIMD_WPAL_W1PAL_L 0x07 +#define FIMD_WPAL_W1PAL_L_SHT 3 +#define FIMD_WPAL_W2PAL_L 0x01 +#define FIMD_WPAL_W2PAL_L_SHT 6 +#define FIMD_WPAL_W2PAL_H 0x06 +#define FIMD_WPAL_W2PAL_H_SHT 8 +#define FIMD_WPAL_W3PAL_L 0x01 +#define FIMD_WPAL_W3PAL_L_SHT 7 +#define FIMD_WPAL_W3PAL_H 0x06 +#define FIMD_WPAL_W3PAL_H_SHT 12 +#define FIMD_WPAL_W4PAL_L 0x01 +#define FIMD_WPAL_W4PAL_L_SHT 8 +#define FIMD_WPAL_W4PAL_H 0x06 +#define FIMD_WPAL_W4PAL_H_SHT 16 + +/* Trigger control registers */ +#define FIMD_TRIGCON 0x01A4 +#define FIMD_TRIGCON_ROMASK 0x00000004 + +/* LCD I80 Interface Control */ +#define FIMD_I80IFCON_START 0x01B0 +#define FIMD_I80IFCON_END 0x01BC +/* Color gain control register */ +#define FIMD_COLORGAINCON 0x01C0 +/* LCD i80 Interface Command Control */ +#define FIMD_LDI_CMDCON0 0x01D0 +#define FIMD_LDI_CMDCON1 0x01D4 +/* I80 System Interface Manual Command Control */ +#define FIMD_SIFCCON0 0x01E0 +#define FIMD_SIFCCON2 0x01E8 + +/* Hue Control Registers */ +#define FIMD_HUECOEFCR_START 0x01EC +#define FIMD_HUECOEFCR_END 0x01F4 +#define FIMD_HUECOEFCB_START 0x01FC +#define FIMD_HUECOEFCB_END 0x0208 +#define FIMD_HUEOFFSET 0x020C + +/* Video interrupt control registers */ +#define FIMD_VIDINT_INTFIFOPEND (1 << 0) +#define FIMD_VIDINT_INTFRMPEND (1 << 1) +#define FIMD_VIDINT_INTI80PEND (1 << 2) +#define FIMD_VIDINT_INTEN (1 << 0) +#define FIMD_VIDINT_INTFIFOEN (1 << 1) +#define FIMD_VIDINT_INTFRMEN (1 << 12) +#define FIMD_VIDINT_I80IFDONE (1 << 17) + +/* Window blend equation control registers */ +#define FIMD_BLENDEQ_START 0x0244 +#define FIMD_BLENDEQ_END 0x0250 +#define FIMD_BLENDCON 0x0260 +#define FIMD_ALPHA_8BIT (1 << 0) +#define FIMD_BLENDEQ_COEF_MASK 0xF + +/* Window RTQOS Control Registers */ +#define FIMD_WRTQOSCON_START 0x0264 +#define FIMD_WRTQOSCON_END 0x0274 + +/* LCD I80 Interface Command */ +#define FIMD_I80IFCMD_START 0x0280 +#define FIMD_I80IFCMD_END 0x02AC + +/* Shadow windows control registers */ +#define FIMD_SHD_ADD0_START 0x40A0 +#define FIMD_SHD_ADD0_END 0x40C0 +#define FIMD_SHD_ADD1_START 0x40D0 +#define FIMD_SHD_ADD1_END 0x40F0 +#define FIMD_SHD_ADD2_START 0x4100 +#define FIMD_SHD_ADD2_END 0x4110 + +/* Palette memory */ +#define FIMD_PAL_MEM_START 0x2400 +#define FIMD_PAL_MEM_END 0x37FC +/* Palette memory aliases for windows 0 and 1 */ +#define FIMD_PALMEM_AL_START 0x0400 +#define FIMD_PALMEM_AL_END 0x0BFC + +typedef struct { + uint8_t r, g, b; + /* D[31..24]dummy, D[23..16]rAlpha, D[15..8]gAlpha, D[7..0]bAlpha */ + uint32_t a; +} rgba; +#define RGBA_SIZE 7 + +typedef void pixel_to_rgb_func(uint32_t pixel, rgba *p); +typedef struct Exynos4210fimdWindow Exynos4210fimdWindow; + +struct Exynos4210fimdWindow { + uint32_t wincon; /* Window control register */ + uint32_t buf_start[3]; /* Start address for video frame buffer */ + uint32_t buf_end[3]; /* End address for video frame buffer */ + uint32_t keycon[2]; /* Window color key registers */ + uint32_t keyalpha; /* Color key alpha control register */ + uint32_t winmap; /* Window color map register */ + uint32_t blendeq; /* Window blending equation control register */ + uint32_t rtqoscon; /* Window RTQOS Control Registers */ + uint32_t palette[256]; /* Palette RAM */ + uint32_t shadow_buf_start; /* Start address of shadow frame buffer */ + uint32_t shadow_buf_end; /* End address of shadow frame buffer */ + uint32_t shadow_buf_size; /* Virtual shadow screen width */ + + pixel_to_rgb_func *pixel_to_rgb; + void (*draw_line)(Exynos4210fimdWindow *w, uint8_t *src, uint8_t *dst, + bool blend); + uint32_t (*get_alpha)(Exynos4210fimdWindow *w, uint32_t pix_a); + uint16_t lefttop_x, lefttop_y; /* VIDOSD0 register */ + uint16_t rightbot_x, rightbot_y; /* VIDOSD1 register */ + uint32_t osdsize; /* VIDOSD2&3 register */ + uint32_t alpha_val[2]; /* VIDOSD2&3, VIDWALPHA registers */ + uint16_t virtpage_width; /* VIDWADD2 register */ + uint16_t virtpage_offsize; /* VIDWADD2 register */ + MemoryRegionSection mem_section; /* RAM fragment containing framebuffer */ + uint8_t *host_fb_addr; /* Host pointer to window's framebuffer */ + target_phys_addr_t fb_len; /* Framebuffer length */ +}; + +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + DisplayState *console; + qemu_irq irq[3]; + + uint32_t vidcon[4]; /* Video main control registers 0-3 */ + uint32_t vidtcon[4]; /* Video time control registers 0-3 */ + uint32_t shadowcon; /* Window shadow control register */ + uint32_t winchmap; /* Channel mapping control register */ + uint32_t vidintcon[2]; /* Video interrupt control registers */ + uint32_t dithmode; /* Dithering control register */ + uint32_t wpalcon[2]; /* Window palette control registers */ + uint32_t trigcon; /* Trigger control register */ + uint32_t i80ifcon[4]; /* I80 interface control registers */ + uint32_t colorgaincon; /* Color gain control register */ + uint32_t ldi_cmdcon[2]; /* LCD I80 interface command control */ + uint32_t sifccon[3]; /* I80 System Interface Manual Command Control */ + uint32_t huecoef_cr[4]; /* Hue control registers */ + uint32_t huecoef_cb[4]; /* Hue control registers */ + uint32_t hueoffset; /* Hue offset control register */ + uint32_t blendcon; /* Blending control register */ + uint32_t i80ifcmd[12]; /* LCD I80 Interface Command */ + + Exynos4210fimdWindow window[5]; /* Window-specific registers */ + uint8_t *ifb; /* Internal frame buffer */ + bool invalidate; /* Image needs to be redrawn */ + bool enabled; /* Display controller is enabled */ +} Exynos4210fimdState; + +/* Perform byte/halfword/word swap of data according to WINCON */ +static inline void fimd_swap_data(unsigned int swap_ctl, uint64_t *data) +{ + int i; + uint64_t res; + uint64_t x = *data; + + if (swap_ctl & FIMD_WINCON_SWAP_BITS) { + res = 0; + for (i = 0; i < 64; i++) { + if (x & (1ULL << (64 - i))) { + res |= (1ULL << i); + } + } + x = res; + } + + if (swap_ctl & FIMD_WINCON_SWAP_BYTE) { + x = bswap64(x); + } + + if (swap_ctl & FIMD_WINCON_SWAP_HWORD) { + x = ((x & 0x000000000000FFFFULL) << 48) | + ((x & 0x00000000FFFF0000ULL) << 16) | + ((x & 0x0000FFFF00000000ULL) >> 16) | + ((x & 0xFFFF000000000000ULL) >> 48); + } + + if (swap_ctl & FIMD_WINCON_SWAP_WORD) { + x = ((x & 0x00000000FFFFFFFFULL) << 32) | + ((x & 0xFFFFFFFF00000000ULL) >> 32); + } + + *data = x; +} + +/* Conversion routines of Pixel data from frame buffer area to internal RGBA + * pixel representation. + * Every color component internally represented as 8-bit value. If original + * data has less than 8 bit for component, data is extended to 8 bit. For + * example, if blue component has only two possible values 0 and 1 it will be + * extended to 0 and 0xFF */ + +/* One bit for alpha representation */ +#define DEF_PIXEL_TO_RGB_A1(N, R, G, B) \ +static void N(uint32_t pixel, rgba *p) \ +{ \ + p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \ + ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \ + pixel >>= (B); \ + p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \ + ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \ + pixel >>= (G); \ + p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \ + ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \ + pixel >>= (R); \ + p->a = (pixel & 0x1); \ +} + +DEF_PIXEL_TO_RGB_A1(pixel_a444_to_rgb, 4, 4, 4) +DEF_PIXEL_TO_RGB_A1(pixel_a555_to_rgb, 5, 5, 5) +DEF_PIXEL_TO_RGB_A1(pixel_a666_to_rgb, 6, 6, 6) +DEF_PIXEL_TO_RGB_A1(pixel_a665_to_rgb, 6, 6, 5) +DEF_PIXEL_TO_RGB_A1(pixel_a888_to_rgb, 8, 8, 8) +DEF_PIXEL_TO_RGB_A1(pixel_a887_to_rgb, 8, 8, 7) + +/* Alpha component is always zero */ +#define DEF_PIXEL_TO_RGB_A0(N, R, G, B) \ +static void N(uint32_t pixel, rgba *p) \ +{ \ + p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \ + ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \ + pixel >>= (B); \ + p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \ + ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \ + pixel >>= (G); \ + p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \ + ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \ + p->a = 0x0; \ +} + +DEF_PIXEL_TO_RGB_A0(pixel_565_to_rgb, 5, 6, 5) +DEF_PIXEL_TO_RGB_A0(pixel_555_to_rgb, 5, 5, 5) +DEF_PIXEL_TO_RGB_A0(pixel_666_to_rgb, 6, 6, 6) +DEF_PIXEL_TO_RGB_A0(pixel_888_to_rgb, 8, 8, 8) + +/* Alpha component has some meaningful value */ +#define DEF_PIXEL_TO_RGB_A(N, R, G, B, A) \ +static void N(uint32_t pixel, rgba *p) \ +{ \ + p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \ + ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \ + pixel >>= (B); \ + p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \ + ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \ + pixel >>= (G); \ + p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \ + ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \ + pixel >>= (R); \ + p->a = (pixel & ((1 << (A)) - 1)) << (8 - (A)) | \ + ((pixel >> (2 * (A) - 8)) & ((1 << (8 - (A))) - 1)); \ + p->a = p->a | (p->a << 8) | (p->a << 16); \ +} + +DEF_PIXEL_TO_RGB_A(pixel_4444_to_rgb, 4, 4, 4, 4) +DEF_PIXEL_TO_RGB_A(pixel_8888_to_rgb, 8, 8, 8, 8) + +/* Lookup table to extent 2-bit color component to 8 bit */ +static const uint8_t pixel_lutable_2b[4] = { + 0x0, 0x55, 0xAA, 0xFF +}; +/* Lookup table to extent 3-bit color component to 8 bit */ +static const uint8_t pixel_lutable_3b[8] = { + 0x0, 0x24, 0x49, 0x6D, 0x92, 0xB6, 0xDB, 0xFF +}; +/* Special case for a232 bpp mode */ +static void pixel_a232_to_rgb(uint32_t pixel, rgba *p) +{ + p->b = pixel_lutable_2b[(pixel & 0x3)]; + pixel >>= 2; + p->g = pixel_lutable_3b[(pixel & 0x7)]; + pixel >>= 3; + p->r = pixel_lutable_2b[(pixel & 0x3)]; + pixel >>= 2; + p->a = (pixel & 0x1); +} + +/* Special case for (5+1, 5+1, 5+1) mode. Data bit 15 is common LSB + * for all three color components */ +static void pixel_1555_to_rgb(uint32_t pixel, rgba *p) +{ + uint8_t comm = (pixel >> 15) & 1; + p->b = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3); + pixel >>= 5; + p->g = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3); + pixel >>= 5; + p->r = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3); + p->a = 0x0; +} + +/* Put/get pixel to/from internal LCD Controller framebuffer */ + +static int put_pixel_ifb(const rgba p, uint8_t *d) +{ + *(uint8_t *)d++ = p.r; + *(uint8_t *)d++ = p.g; + *(uint8_t *)d++ = p.b; + *(uint32_t *)d = p.a; + return RGBA_SIZE; +} + +static int get_pixel_ifb(const uint8_t *s, rgba *p) +{ + p->r = *(uint8_t *)s++; + p->g = *(uint8_t *)s++; + p->b = *(uint8_t *)s++; + p->a = (*(uint32_t *)s) & 0x00FFFFFF; + return RGBA_SIZE; +} + +static pixel_to_rgb_func *palette_data_format[8] = { + [0] = pixel_565_to_rgb, + [1] = pixel_a555_to_rgb, + [2] = pixel_666_to_rgb, + [3] = pixel_a665_to_rgb, + [4] = pixel_a666_to_rgb, + [5] = pixel_888_to_rgb, + [6] = pixel_a888_to_rgb, + [7] = pixel_8888_to_rgb +}; + +/* Returns Index in palette data formats table for given window number WINDOW */ +static uint32_t +exynos4210_fimd_palette_format(Exynos4210fimdState *s, int window) +{ + uint32_t ret; + + switch (window) { + case 0: + ret = (s->wpalcon[1] >> FIMD_WPAL_W0PAL_L_SHT) & FIMD_WPAL_W0PAL_L; + if (ret != 7) { + ret = 6 - ret; + } + break; + case 1: + ret = (s->wpalcon[1] >> FIMD_WPAL_W1PAL_L_SHT) & FIMD_WPAL_W1PAL_L; + if (ret != 7) { + ret = 6 - ret; + } + break; + case 2: + ret = ((s->wpalcon[0] >> FIMD_WPAL_W2PAL_H_SHT) & FIMD_WPAL_W2PAL_H) | + ((s->wpalcon[1] >> FIMD_WPAL_W2PAL_L_SHT) & FIMD_WPAL_W2PAL_L); + break; + case 3: + ret = ((s->wpalcon[0] >> FIMD_WPAL_W3PAL_H_SHT) & FIMD_WPAL_W3PAL_H) | + ((s->wpalcon[1] >> FIMD_WPAL_W3PAL_L_SHT) & FIMD_WPAL_W3PAL_L); + break; + case 4: + ret = ((s->wpalcon[0] >> FIMD_WPAL_W4PAL_H_SHT) & FIMD_WPAL_W4PAL_H) | + ((s->wpalcon[1] >> FIMD_WPAL_W4PAL_L_SHT) & FIMD_WPAL_W4PAL_L); + break; + default: + hw_error("exynos4210.fimd: incorrect window number %d\n", window); + ret = 0; + break; + } + return ret; +} + +#define FIMD_1_MINUS_COLOR(x) \ + ((0xFF - ((x) & 0xFF)) | (0xFF00 - ((x) & 0xFF00)) | \ + (0xFF0000 - ((x) & 0xFF0000))) +#define EXTEND_LOWER_HALFBYTE(x) (((x) & 0xF0F0F) | (((x) << 4) & 0xF0F0F0)) +#define EXTEND_UPPER_HALFBYTE(x) (((x) & 0xF0F0F0) | (((x) >> 4) & 0xF0F0F)) + +/* Multiply three lower bytes of two 32-bit words with each other. + * Each byte with values 0-255 is considered as a number with possible values + * in a range [0 - 1] */ +static inline uint32_t fimd_mult_each_byte(uint32_t a, uint32_t b) +{ + uint32_t tmp; + uint32_t ret; + + ret = ((tmp = (((a & 0xFF) * (b & 0xFF)) / 0xFF)) > 0xFF) ? 0xFF : tmp; + ret |= ((tmp = ((((a >> 8) & 0xFF) * ((b >> 8) & 0xFF)) / 0xFF)) > 0xFF) ? + 0xFF00 : tmp << 8; + ret |= ((tmp = ((((a >> 16) & 0xFF) * ((b >> 16) & 0xFF)) / 0xFF)) > 0xFF) ? + 0xFF0000 : tmp << 16; + return ret; +} + +/* For each corresponding bytes of two 32-bit words: (a*b + c*d) + * Byte values 0-255 are mapped to a range [0 .. 1] */ +static inline uint32_t +fimd_mult_and_sum_each_byte(uint32_t a, uint32_t b, uint32_t c, uint32_t d) +{ + uint32_t tmp; + uint32_t ret; + + ret = ((tmp = (((a & 0xFF) * (b & 0xFF) + (c & 0xFF) * (d & 0xFF)) / 0xFF)) + > 0xFF) ? 0xFF : tmp; + ret |= ((tmp = ((((a >> 8) & 0xFF) * ((b >> 8) & 0xFF) + ((c >> 8) & 0xFF) * + ((d >> 8) & 0xFF)) / 0xFF)) > 0xFF) ? 0xFF00 : tmp << 8; + ret |= ((tmp = ((((a >> 16) & 0xFF) * ((b >> 16) & 0xFF) + + ((c >> 16) & 0xFF) * ((d >> 16) & 0xFF)) / 0xFF)) > 0xFF) ? + 0xFF0000 : tmp << 16; + return ret; +} + +/* These routines cover all possible sources of window's transparent factor + * used in blending equation. Choice of routine is affected by WPALCON + * registers, BLENDCON register and window's WINCON register */ + +static uint32_t fimd_get_alpha_pix(Exynos4210fimdWindow *w, uint32_t pix_a) +{ + return pix_a; +} + +static uint32_t +fimd_get_alpha_pix_extlow(Exynos4210fimdWindow *w, uint32_t pix_a) +{ + return EXTEND_LOWER_HALFBYTE(pix_a); +} + +static uint32_t +fimd_get_alpha_pix_exthigh(Exynos4210fimdWindow *w, uint32_t pix_a) +{ + return EXTEND_UPPER_HALFBYTE(pix_a); +} + +static uint32_t fimd_get_alpha_mult(Exynos4210fimdWindow *w, uint32_t pix_a) +{ + return fimd_mult_each_byte(pix_a, w->alpha_val[0]); +} + +static uint32_t fimd_get_alpha_mult_ext(Exynos4210fimdWindow *w, uint32_t pix_a) +{ + return fimd_mult_each_byte(EXTEND_LOWER_HALFBYTE(pix_a), + EXTEND_UPPER_HALFBYTE(w->alpha_val[0])); +} + +static uint32_t fimd_get_alpha_aen(Exynos4210fimdWindow *w, uint32_t pix_a) +{ + return w->alpha_val[pix_a]; +} + +static uint32_t fimd_get_alpha_aen_ext(Exynos4210fimdWindow *w, uint32_t pix_a) +{ + return EXTEND_UPPER_HALFBYTE(w->alpha_val[pix_a]); +} + +static uint32_t fimd_get_alpha_sel(Exynos4210fimdWindow *w, uint32_t pix_a) +{ + return w->alpha_val[(w->wincon & FIMD_WINCON_ALPHA_SEL) ? 1 : 0]; +} + +static uint32_t fimd_get_alpha_sel_ext(Exynos4210fimdWindow *w, uint32_t pix_a) +{ + return EXTEND_UPPER_HALFBYTE(w->alpha_val[(w->wincon & + FIMD_WINCON_ALPHA_SEL) ? 1 : 0]); +} + +/* Updates currently active alpha value get function for specified window */ +static void fimd_update_get_alpha(Exynos4210fimdState *s, int win) +{ + Exynos4210fimdWindow *w = &s->window[win]; + const bool alpha_is_8bit = s->blendcon & FIMD_ALPHA_8BIT; + + if (w->wincon & FIMD_WINCON_BLD_PIX) { + if ((w->wincon & FIMD_WINCON_ALPHA_SEL) && WIN_BPP_MODE_WITH_ALPHA(w)) { + /* In this case, alpha component contains meaningful value */ + if (w->wincon & FIMD_WINCON_ALPHA_MUL) { + w->get_alpha = alpha_is_8bit ? + fimd_get_alpha_mult : fimd_get_alpha_mult_ext; + } else { + w->get_alpha = alpha_is_8bit ? + fimd_get_alpha_pix : fimd_get_alpha_pix_extlow; + } + } else { + if (IS_PALETTIZED_MODE(w) && + PAL_MODE_WITH_ALPHA(exynos4210_fimd_palette_format(s, win))) { + /* Alpha component has 8-bit numeric value */ + w->get_alpha = alpha_is_8bit ? + fimd_get_alpha_pix : fimd_get_alpha_pix_exthigh; + } else { + /* Alpha has only two possible values (AEN) */ + w->get_alpha = alpha_is_8bit ? + fimd_get_alpha_aen : fimd_get_alpha_aen_ext; + } + } + } else { + w->get_alpha = alpha_is_8bit ? fimd_get_alpha_sel : + fimd_get_alpha_sel_ext; + } +} + +/* Blends current window's (w) pixel (foreground pixel *ret) with background + * window (w_blend) pixel p_bg according to formula: + * NEW_COLOR = a_coef x FG_PIXEL_COLOR + b_coef x BG_PIXEL_COLOR + * NEW_ALPHA = p_coef x FG_ALPHA + q_coef x BG_ALPHA + */ +static void +exynos4210_fimd_blend_pixel(Exynos4210fimdWindow *w, rgba p_bg, rgba *ret) +{ + rgba p_fg = *ret; + uint32_t bg_color = ((p_bg.r & 0xFF) << 16) | ((p_bg.g & 0xFF) << 8) | + (p_bg.b & 0xFF); + uint32_t fg_color = ((p_fg.r & 0xFF) << 16) | ((p_fg.g & 0xFF) << 8) | + (p_fg.b & 0xFF); + uint32_t alpha_fg = p_fg.a; + int i; + /* It is possible that blending equation parameters a and b do not + * depend on window BLENEQ register. Account for this with first_coef */ + enum { A_COEF = 0, B_COEF = 1, P_COEF = 2, Q_COEF = 3, COEF_NUM = 4}; + uint32_t first_coef = A_COEF; + uint32_t blend_param[COEF_NUM]; + + if (w->keycon[0] & FIMD_WKEYCON0_KEYEN) { + uint32_t colorkey = (w->keycon[1] & + ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) & FIMD_WKEYCON0_COMPKEY; + + if ((w->keycon[0] & FIMD_WKEYCON0_DIRCON) && + (bg_color & ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) == colorkey) { + /* Foreground pixel is displayed */ + if (w->keycon[0] & FIMD_WKEYCON0_KEYBLEN) { + alpha_fg = w->keyalpha; + blend_param[A_COEF] = alpha_fg; + blend_param[B_COEF] = FIMD_1_MINUS_COLOR(alpha_fg); + } else { + alpha_fg = 0; + blend_param[A_COEF] = 0xFFFFFF; + blend_param[B_COEF] = 0x0; + } + first_coef = P_COEF; + } else if ((w->keycon[0] & FIMD_WKEYCON0_DIRCON) == 0 && + (fg_color & ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) == colorkey) { + /* Background pixel is displayed */ + if (w->keycon[0] & FIMD_WKEYCON0_KEYBLEN) { + alpha_fg = w->keyalpha; + blend_param[A_COEF] = alpha_fg; + blend_param[B_COEF] = FIMD_1_MINUS_COLOR(alpha_fg); + } else { + alpha_fg = 0; + blend_param[A_COEF] = 0x0; + blend_param[B_COEF] = 0xFFFFFF; + } + first_coef = P_COEF; + } + } + + for (i = first_coef; i < COEF_NUM; i++) { + switch ((w->blendeq >> i * 6) & FIMD_BLENDEQ_COEF_MASK) { + case 0: + blend_param[i] = 0; + break; + case 1: + blend_param[i] = 0xFFFFFF; + break; + case 2: + blend_param[i] = alpha_fg; + break; + case 3: + blend_param[i] = FIMD_1_MINUS_COLOR(alpha_fg); + break; + case 4: + blend_param[i] = p_bg.a; + break; + case 5: + blend_param[i] = FIMD_1_MINUS_COLOR(p_bg.a); + break; + case 6: + blend_param[i] = w->alpha_val[0]; + break; + case 10: + blend_param[i] = fg_color; + break; + case 11: + blend_param[i] = FIMD_1_MINUS_COLOR(fg_color); + break; + case 12: + blend_param[i] = bg_color; + break; + case 13: + blend_param[i] = FIMD_1_MINUS_COLOR(bg_color); + break; + default: + hw_error("exynos4210.fimd: blend equation coef illegal value\n"); + break; + } + } + + fg_color = fimd_mult_and_sum_each_byte(bg_color, blend_param[B_COEF], + fg_color, blend_param[A_COEF]); + ret->b = fg_color & 0xFF; + fg_color >>= 8; + ret->g = fg_color & 0xFF; + fg_color >>= 8; + ret->r = fg_color & 0xFF; + ret->a = fimd_mult_and_sum_each_byte(alpha_fg, blend_param[P_COEF], + p_bg.a, blend_param[Q_COEF]); +} + +/* These routines read data from video frame buffer in system RAM, convert + * this data to display controller internal representation, if necessary, + * perform pixel blending with data, currently presented in internal buffer. + * Result is stored in display controller internal frame buffer. */ + +/* Draw line with index in palette table in RAM frame buffer data */ +#define DEF_DRAW_LINE_PALETTE(N) \ +static void glue(draw_line_palette_, N)(Exynos4210fimdWindow *w, uint8_t *src, \ + uint8_t *dst, bool blend) \ +{ \ + int width = w->rightbot_x - w->lefttop_x + 1; \ + uint8_t *ifb = dst; \ + uint8_t swap = (w->wincon & FIMD_WINCON_SWAP) >> FIMD_WINCON_SWAP_SHIFT; \ + uint64_t data; \ + rgba p, p_old; \ + int i; \ + do { \ + data = ldq_raw((void *)src); \ + src += 8; \ + fimd_swap_data(swap, &data); \ + for (i = (64 / (N) - 1); i >= 0; i--) { \ + w->pixel_to_rgb(w->palette[(data >> ((N) * i)) & \ + ((1ULL << (N)) - 1)], &p); \ + p.a = w->get_alpha(w, p.a); \ + if (blend) { \ + ifb += get_pixel_ifb(ifb, &p_old); \ + exynos4210_fimd_blend_pixel(w, p_old, &p); \ + } \ + dst += put_pixel_ifb(p, dst); \ + } \ + width -= (64 / (N)); \ + } while (width > 0); \ +} + +/* Draw line with direct color value in RAM frame buffer data */ +#define DEF_DRAW_LINE_NOPALETTE(N) \ +static void glue(draw_line_, N)(Exynos4210fimdWindow *w, uint8_t *src, \ + uint8_t *dst, bool blend) \ +{ \ + int width = w->rightbot_x - w->lefttop_x + 1; \ + uint8_t *ifb = dst; \ + uint8_t swap = (w->wincon & FIMD_WINCON_SWAP) >> FIMD_WINCON_SWAP_SHIFT; \ + uint64_t data; \ + rgba p, p_old; \ + int i; \ + do { \ + data = ldq_raw((void *)src); \ + src += 8; \ + fimd_swap_data(swap, &data); \ + for (i = (64 / (N) - 1); i >= 0; i--) { \ + w->pixel_to_rgb((data >> ((N) * i)) & ((1ULL << (N)) - 1), &p); \ + p.a = w->get_alpha(w, p.a); \ + if (blend) { \ + ifb += get_pixel_ifb(ifb, &p_old); \ + exynos4210_fimd_blend_pixel(w, p_old, &p); \ + } \ + dst += put_pixel_ifb(p, dst); \ + } \ + width -= (64 / (N)); \ + } while (width > 0); \ +} + +DEF_DRAW_LINE_PALETTE(1) +DEF_DRAW_LINE_PALETTE(2) +DEF_DRAW_LINE_PALETTE(4) +DEF_DRAW_LINE_PALETTE(8) +DEF_DRAW_LINE_NOPALETTE(8) /* 8bpp mode has palette and non-palette versions */ +DEF_DRAW_LINE_NOPALETTE(16) +DEF_DRAW_LINE_NOPALETTE(32) + +/* Special draw line routine for window color map case */ +static void draw_line_mapcolor(Exynos4210fimdWindow *w, uint8_t *src, + uint8_t *dst, bool blend) +{ + rgba p, p_old; + uint8_t *ifb = dst; + int width = w->rightbot_x - w->lefttop_x + 1; + uint32_t map_color = w->winmap & FIMD_WINMAP_COLOR_MASK; + + do { + pixel_888_to_rgb(map_color, &p); + p.a = w->get_alpha(w, p.a); + if (blend) { + ifb += get_pixel_ifb(ifb, &p_old); + exynos4210_fimd_blend_pixel(w, p_old, &p); + } + dst += put_pixel_ifb(p, dst); + } while (--width); +} + +/* Write RGB to QEMU's GraphicConsole framebuffer */ + +static int put_to_qemufb_pixel8(const rgba p, uint8_t *d) +{ + uint32_t pixel = rgb_to_pixel8(p.r, p.g, p.b); + *(uint8_t *)d = pixel; + return 1; +} + +static int put_to_qemufb_pixel15(const rgba p, uint8_t *d) +{ + uint32_t pixel = rgb_to_pixel15(p.r, p.g, p.b); + *(uint16_t *)d = pixel; + return 2; +} + +static int put_to_qemufb_pixel16(const rgba p, uint8_t *d) +{ + uint32_t pixel = rgb_to_pixel16(p.r, p.g, p.b); + *(uint16_t *)d = pixel; + return 2; +} + +static int put_to_qemufb_pixel24(const rgba p, uint8_t *d) +{ + uint32_t pixel = rgb_to_pixel24(p.r, p.g, p.b); + *(uint8_t *)d++ = (pixel >> 0) & 0xFF; + *(uint8_t *)d++ = (pixel >> 8) & 0xFF; + *(uint8_t *)d++ = (pixel >> 16) & 0xFF; + return 3; +} + +static int put_to_qemufb_pixel32(const rgba p, uint8_t *d) +{ + uint32_t pixel = rgb_to_pixel24(p.r, p.g, p.b); + *(uint32_t *)d = pixel; + return 4; +} + +/* Routine to copy pixel from internal buffer to QEMU buffer */ +static int (*put_pixel_toqemu)(const rgba p, uint8_t *pixel); +static inline void fimd_update_putpix_qemu(int bpp) +{ + switch (bpp) { + case 8: + put_pixel_toqemu = put_to_qemufb_pixel8; + break; + case 15: + put_pixel_toqemu = put_to_qemufb_pixel15; + break; + case 16: + put_pixel_toqemu = put_to_qemufb_pixel16; + break; + case 24: + put_pixel_toqemu = put_to_qemufb_pixel24; + break; + case 32: + put_pixel_toqemu = put_to_qemufb_pixel32; + break; + default: + hw_error("exynos4210.fimd: unsupported BPP (%d)", bpp); + break; + } +} + +/* Routine to copy a line from internal frame buffer to QEMU display */ +static void fimd_copy_line_toqemu(int width, uint8_t *src, uint8_t *dst) +{ + rgba p; + + do { + src += get_pixel_ifb(src, &p); + dst += put_pixel_toqemu(p, dst); + } while (--width); +} + +/* Parse BPPMODE_F = WINCON1[5:2] bits */ +static void exynos4210_fimd_update_win_bppmode(Exynos4210fimdState *s, int win) +{ + Exynos4210fimdWindow *w = &s->window[win]; + + if (w->winmap & FIMD_WINMAP_EN) { + w->draw_line = draw_line_mapcolor; + return; + } + + switch (WIN_BPP_MODE(w)) { + case 0: + w->draw_line = draw_line_palette_1; + w->pixel_to_rgb = + palette_data_format[exynos4210_fimd_palette_format(s, win)]; + break; + case 1: + w->draw_line = draw_line_palette_2; + w->pixel_to_rgb = + palette_data_format[exynos4210_fimd_palette_format(s, win)]; + break; + case 2: + w->draw_line = draw_line_palette_4; + w->pixel_to_rgb = + palette_data_format[exynos4210_fimd_palette_format(s, win)]; + break; + case 3: + w->draw_line = draw_line_palette_8; + w->pixel_to_rgb = + palette_data_format[exynos4210_fimd_palette_format(s, win)]; + break; + case 4: + w->draw_line = draw_line_8; + w->pixel_to_rgb = pixel_a232_to_rgb; + break; + case 5: + w->draw_line = draw_line_16; + w->pixel_to_rgb = pixel_565_to_rgb; + break; + case 6: + w->draw_line = draw_line_16; + w->pixel_to_rgb = pixel_a555_to_rgb; + break; + case 7: + w->draw_line = draw_line_16; + w->pixel_to_rgb = pixel_1555_to_rgb; + break; + case 8: + w->draw_line = draw_line_32; + w->pixel_to_rgb = pixel_666_to_rgb; + break; + case 9: + w->draw_line = draw_line_32; + w->pixel_to_rgb = pixel_a665_to_rgb; + break; + case 10: + w->draw_line = draw_line_32; + w->pixel_to_rgb = pixel_a666_to_rgb; + break; + case 11: + w->draw_line = draw_line_32; + w->pixel_to_rgb = pixel_888_to_rgb; + break; + case 12: + w->draw_line = draw_line_32; + w->pixel_to_rgb = pixel_a887_to_rgb; + break; + case 13: + w->draw_line = draw_line_32; + if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon & + FIMD_WINCON_ALPHA_SEL)) { + w->pixel_to_rgb = pixel_8888_to_rgb; + } else { + w->pixel_to_rgb = pixel_a888_to_rgb; + } + break; + case 14: + w->draw_line = draw_line_16; + if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon & + FIMD_WINCON_ALPHA_SEL)) { + w->pixel_to_rgb = pixel_4444_to_rgb; + } else { + w->pixel_to_rgb = pixel_a444_to_rgb; + } + break; + case 15: + w->draw_line = draw_line_16; + w->pixel_to_rgb = pixel_555_to_rgb; + break; + } +} + +#if EXYNOS4210_FIMD_MODE_TRACE > 0 +static const char *exynos4210_fimd_get_bppmode(int mode_code) +{ + switch (mode_code) { + case 0: + return "1 bpp"; + case 1: + return "2 bpp"; + case 2: + return "4 bpp"; + case 3: + return "8 bpp (palettized)"; + case 4: + return "8 bpp (non-palettized, A: 1-R:2-G:3-B:2)"; + case 5: + return "16 bpp (non-palettized, R:5-G:6-B:5)"; + case 6: + return "16 bpp (non-palettized, A:1-R:5-G:5-B:5)"; + case 7: + return "16 bpp (non-palettized, I :1-R:5-G:5-B:5)"; + case 8: + return "Unpacked 18 bpp (non-palettized, R:6-G:6-B:6)"; + case 9: + return "Unpacked 18bpp (non-palettized,A:1-R:6-G:6-B:5)"; + case 10: + return "Unpacked 19bpp (non-palettized,A:1-R:6-G:6-B:6)"; + case 11: + return "Unpacked 24 bpp (non-palettized R:8-G:8-B:8)"; + case 12: + return "Unpacked 24 bpp (non-palettized A:1-R:8-G:8-B:7)"; + case 13: + return "Unpacked 25 bpp (non-palettized A:1-R:8-G:8-B:8)"; + case 14: + return "Unpacked 13 bpp (non-palettized A:1-R:4-G:4-B:4)"; + case 15: + return "Unpacked 15 bpp (non-palettized R:5-G:5-B:5)"; + default: + return "Non-existing bpp mode"; + } +} + +static inline void exynos4210_fimd_trace_bppmode(Exynos4210fimdState *s, + int win_num, uint32_t val) +{ + Exynos4210fimdWindow *w = &s->window[win_num]; + + if (w->winmap & FIMD_WINMAP_EN) { + printf("QEMU FIMD: Window %d is mapped with MAPCOLOR=0x%x\n", + win_num, w->winmap & 0xFFFFFF); + return; + } + + if ((val != 0xFFFFFFFF) && ((w->wincon >> 2) & 0xF) == ((val >> 2) & 0xF)) { + return; + } + printf("QEMU FIMD: Window %d BPP mode set to %s\n", win_num, + exynos4210_fimd_get_bppmode((val >> 2) & 0xF)); +} +#else +static inline void exynos4210_fimd_trace_bppmode(Exynos4210fimdState *s, + int win_num, uint32_t val) +{ + +} +#endif + +static inline int fimd_get_buffer_id(Exynos4210fimdWindow *w) +{ + switch (w->wincon & FIMD_WINCON_BUFSTATUS) { + case FIMD_WINCON_BUF0_STAT: + return 0; + case FIMD_WINCON_BUF1_STAT: + return 1; + case FIMD_WINCON_BUF2_STAT: + return 2; + default: + DPRINT_ERROR("Non-existent buffer index\n"); + return 0; + } +} + +/* Updates specified window's MemorySection based on values of WINCON, + * VIDOSDA, VIDOSDB, VIDWADDx and SHADOWCON registers */ +static void fimd_update_memory_section(Exynos4210fimdState *s, unsigned win) +{ + Exynos4210fimdWindow *w = &s->window[win]; + target_phys_addr_t fb_start_addr, fb_mapped_len; + + if (!s->enabled || !(w->wincon & FIMD_WINCON_ENWIN) || + FIMD_WINDOW_PROTECTED(s->shadowcon, win)) { + return; + } + + if (w->host_fb_addr) { + cpu_physical_memory_unmap(w->host_fb_addr, w->fb_len, 0, 0); + w->host_fb_addr = NULL; + w->fb_len = 0; + } + + fb_start_addr = w->buf_start[fimd_get_buffer_id(w)]; + /* Total number of bytes of virtual screen used by current window */ + w->fb_len = fb_mapped_len = (w->virtpage_width + w->virtpage_offsize) * + (w->rightbot_y - w->lefttop_y + 1); + w->mem_section = memory_region_find(sysbus_address_space(&s->busdev), + fb_start_addr, w->fb_len); + assert(w->mem_section.mr); + assert(w->mem_section.offset_within_address_space == fb_start_addr); + DPRINT_TRACE("Window %u framebuffer changed: address=0x%08x, len=0x%x\n", + win, fb_start_addr, w->fb_len); + + if (w->mem_section.size != w->fb_len || + !memory_region_is_ram(w->mem_section.mr)) { + DPRINT_ERROR("Failed to find window %u framebuffer region\n", win); + goto error_return; + } + + w->host_fb_addr = cpu_physical_memory_map(fb_start_addr, &fb_mapped_len, 0); + if (!w->host_fb_addr) { + DPRINT_ERROR("Failed to map window %u framebuffer\n", win); + goto error_return; + } + + if (fb_mapped_len != w->fb_len) { + DPRINT_ERROR("Window %u mapped framebuffer length is less then " + "expected\n", win); + cpu_physical_memory_unmap(w->host_fb_addr, fb_mapped_len, 0, 0); + goto error_return; + } + return; + +error_return: + w->mem_section.mr = NULL; + w->mem_section.size = 0; + w->host_fb_addr = NULL; + w->fb_len = 0; +} + +static void exynos4210_fimd_enable(Exynos4210fimdState *s, bool enabled) +{ + if (enabled && !s->enabled) { + unsigned w; + s->enabled = true; + for (w = 0; w < NUM_OF_WINDOWS; w++) { + fimd_update_memory_section(s, w); + } + } + s->enabled = enabled; + DPRINT_TRACE("display controller %s\n", enabled ? "enabled" : "disabled"); +} + +static inline uint32_t unpack_upper_4(uint32_t x) +{ + return ((x & 0xF00) << 12) | ((x & 0xF0) << 8) | ((x & 0xF) << 4); +} + +static inline uint32_t pack_upper_4(uint32_t x) +{ + return (((x & 0xF00000) >> 12) | ((x & 0xF000) >> 8) | + ((x & 0xF0) >> 4)) & 0xFFF; +} + +static void exynos4210_fimd_update_irq(Exynos4210fimdState *s) +{ + if (!(s->vidintcon[0] & FIMD_VIDINT_INTEN)) { + qemu_irq_lower(s->irq[0]); + qemu_irq_lower(s->irq[1]); + qemu_irq_lower(s->irq[2]); + return; + } + if ((s->vidintcon[0] & FIMD_VIDINT_INTFIFOEN) && + (s->vidintcon[1] & FIMD_VIDINT_INTFIFOPEND)) { + qemu_irq_raise(s->irq[0]); + } else { + qemu_irq_lower(s->irq[0]); + } + if ((s->vidintcon[0] & FIMD_VIDINT_INTFRMEN) && + (s->vidintcon[1] & FIMD_VIDINT_INTFRMPEND)) { + qemu_irq_raise(s->irq[1]); + } else { + qemu_irq_lower(s->irq[1]); + } + if ((s->vidintcon[0] & FIMD_VIDINT_I80IFDONE) && + (s->vidintcon[1] & FIMD_VIDINT_INTI80PEND)) { + qemu_irq_raise(s->irq[2]); + } else { + qemu_irq_lower(s->irq[2]); + } +} + +static void exynos4210_fimd_invalidate(void *opaque) +{ + Exynos4210fimdState *s = (Exynos4210fimdState *)opaque; + s->invalidate = true; +} + +static void exynos4210_update_resolution(Exynos4210fimdState *s) +{ + /* LCD resolution is stored in VIDEO TIME CONTROL REGISTER 2 */ + uint32_t width = ((s->vidtcon[2] >> FIMD_VIDTCON2_HOR_SHIFT) & + FIMD_VIDTCON2_SIZE_MASK) + 1; + uint32_t height = ((s->vidtcon[2] >> FIMD_VIDTCON2_VER_SHIFT) & + FIMD_VIDTCON2_SIZE_MASK) + 1; + + if (s->ifb == NULL || ds_get_width(s->console) != width || + ds_get_height(s->console) != height) { + DPRINT_L1("Resolution changed from %ux%u to %ux%u\n", + ds_get_width(s->console), ds_get_height(s->console), width, height); + qemu_console_resize(s->console, width, height); + s->ifb = g_realloc(s->ifb, width * height * RGBA_SIZE + 1); + memset(s->ifb, 0, width * height * RGBA_SIZE + 1); + exynos4210_fimd_invalidate(s); + } +} + +static void exynos4210_fimd_update(void *opaque) +{ + Exynos4210fimdState *s = (Exynos4210fimdState *)opaque; + Exynos4210fimdWindow *w; + int i, line; + target_phys_addr_t fb_line_addr, inc_size; + int scrn_height; + int first_line = -1, last_line = -1, scrn_width; + bool blend = false; + uint8_t *host_fb_addr; + bool is_dirty = false; + const int global_width = (s->vidtcon[2] & FIMD_VIDTCON2_SIZE_MASK) + 1; + const int global_height = ((s->vidtcon[2] >> FIMD_VIDTCON2_VER_SHIFT) & + FIMD_VIDTCON2_SIZE_MASK) + 1; + + if (!s || !s->console || !ds_get_bits_per_pixel(s->console) || + !s->enabled) { + return; + } + exynos4210_update_resolution(s); + + for (i = 0; i < NUM_OF_WINDOWS; i++) { + w = &s->window[i]; + if ((w->wincon & FIMD_WINCON_ENWIN) && w->host_fb_addr) { + scrn_height = w->rightbot_y - w->lefttop_y + 1; + scrn_width = w->virtpage_width; + /* Total width of virtual screen page in bytes */ + inc_size = scrn_width + w->virtpage_offsize; + memory_region_sync_dirty_bitmap(w->mem_section.mr); + host_fb_addr = w->host_fb_addr; + fb_line_addr = w->mem_section.offset_within_region; + + for (line = 0; line < scrn_height; line++) { + is_dirty = memory_region_get_dirty(w->mem_section.mr, + fb_line_addr, scrn_width, DIRTY_MEMORY_VGA); + + if (s->invalidate || is_dirty) { + if (first_line == -1) { + first_line = line; + } + last_line = line; + w->draw_line(w, host_fb_addr, s->ifb + + w->lefttop_x * RGBA_SIZE + (w->lefttop_y + line) * + global_width * RGBA_SIZE, blend); + } + host_fb_addr += inc_size; + fb_line_addr += inc_size; + is_dirty = false; + } + memory_region_reset_dirty(w->mem_section.mr, + w->mem_section.offset_within_region, + w->fb_len, DIRTY_MEMORY_VGA); + blend = true; + } + } + + /* Copy resulting image to QEMU_CONSOLE. */ + if (first_line >= 0) { + uint8_t *d; + int bpp; + + bpp = ds_get_bits_per_pixel(s->console); + fimd_update_putpix_qemu(bpp); + bpp = (bpp + 1) >> 3; + d = ds_get_data(s->console); + for (line = first_line; line <= last_line; line++) { + fimd_copy_line_toqemu(global_width, s->ifb + global_width * line * + RGBA_SIZE, d + global_width * line * bpp); + } + dpy_update(s->console, 0, 0, global_width, global_height); + } + s->invalidate = false; + s->vidintcon[1] |= FIMD_VIDINT_INTFRMPEND; + if ((s->vidcon[0] & FIMD_VIDCON0_ENVID_F) == 0) { + exynos4210_fimd_enable(s, false); + } + exynos4210_fimd_update_irq(s); +} + +static void exynos4210_fimd_reset(DeviceState *d) +{ + Exynos4210fimdState *s = DO_UPCAST(Exynos4210fimdState, busdev.qdev, d); + unsigned w; + + DPRINT_TRACE("Display controller reset\n"); + /* Set all display controller registers to 0 */ + memset(&s->vidcon, 0, (uint8_t *)&s->window - (uint8_t *)&s->vidcon); + for (w = 0; w < NUM_OF_WINDOWS; w++) { + memset(&s->window[w], 0, sizeof(Exynos4210fimdWindow)); + s->window[w].blendeq = 0xC2; + exynos4210_fimd_update_win_bppmode(s, w); + exynos4210_fimd_trace_bppmode(s, w, 0xFFFFFFFF); + fimd_update_get_alpha(s, w); + } + + if (s->ifb != NULL) { + g_free(s->ifb); + } + s->ifb = NULL; + + exynos4210_fimd_invalidate(s); + exynos4210_fimd_enable(s, false); + /* Some registers have non-zero initial values */ + s->winchmap = 0x7D517D51; + s->colorgaincon = 0x10040100; + s->huecoef_cr[0] = s->huecoef_cr[3] = 0x01000100; + s->huecoef_cb[0] = s->huecoef_cb[3] = 0x01000100; + s->hueoffset = 0x01800080; +} + +static void exynos4210_fimd_write(void *opaque, target_phys_addr_t offset, + uint64_t val, unsigned size) +{ + Exynos4210fimdState *s = (Exynos4210fimdState *)opaque; + unsigned w, i; + uint32_t old_value; + + DPRINT_L2("write offset 0x%08x, value=%llu(0x%08llx)\n", offset, + (long long unsigned int)val, (long long unsigned int)val); + + switch (offset) { + case FIMD_VIDCON0: + if ((val & FIMD_VIDCON0_ENVID_MASK) == FIMD_VIDCON0_ENVID_MASK) { + exynos4210_fimd_enable(s, true); + } else { + if ((val & FIMD_VIDCON0_ENVID) == 0) { + exynos4210_fimd_enable(s, false); + } + } + s->vidcon[0] = val; + break; + case FIMD_VIDCON1: + /* Leave read-only bits as is */ + val = (val & (~FIMD_VIDCON1_ROMASK)) | + (s->vidcon[1] & FIMD_VIDCON1_ROMASK); + s->vidcon[1] = val; + break; + case FIMD_VIDCON2 ... FIMD_VIDCON3: + s->vidcon[(offset) >> 2] = val; + break; + case FIMD_VIDTCON_START ... FIMD_VIDTCON_END: + s->vidtcon[(offset - FIMD_VIDTCON_START) >> 2] = val; + break; + case FIMD_WINCON_START ... FIMD_WINCON_END: + w = (offset - FIMD_WINCON_START) >> 2; + /* Window's current buffer ID */ + i = fimd_get_buffer_id(&s->window[w]); + old_value = s->window[w].wincon; + val = (val & ~FIMD_WINCON_ROMASK) | + (s->window[w].wincon & FIMD_WINCON_ROMASK); + if (w == 0) { + /* Window 0 wincon ALPHA_MUL bit must always be 0 */ + val &= ~FIMD_WINCON_ALPHA_MUL; + } + exynos4210_fimd_trace_bppmode(s, w, val); + switch (val & FIMD_WINCON_BUFSELECT) { + case FIMD_WINCON_BUF0_SEL: + val &= ~FIMD_WINCON_BUFSTATUS; + break; + case FIMD_WINCON_BUF1_SEL: + val = (val & ~FIMD_WINCON_BUFSTAT_H) | FIMD_WINCON_BUFSTAT_L; + break; + case FIMD_WINCON_BUF2_SEL: + if (val & FIMD_WINCON_BUFMODE) { + val = (val & ~FIMD_WINCON_BUFSTAT_L) | FIMD_WINCON_BUFSTAT_H; + } + break; + default: + break; + } + s->window[w].wincon = val; + exynos4210_fimd_update_win_bppmode(s, w); + fimd_update_get_alpha(s, w); + if ((i != fimd_get_buffer_id(&s->window[w])) || + (!(old_value & FIMD_WINCON_ENWIN) && (s->window[w].wincon & + FIMD_WINCON_ENWIN))) { + fimd_update_memory_section(s, w); + } + break; + case FIMD_SHADOWCON: + old_value = s->shadowcon; + s->shadowcon = val; + for (w = 0; w < NUM_OF_WINDOWS; w++) { + if (FIMD_WINDOW_PROTECTED(old_value, w) && + !FIMD_WINDOW_PROTECTED(s->shadowcon, w)) { + fimd_update_memory_section(s, w); + } + } + break; + case FIMD_WINCHMAP: + s->winchmap = val; + break; + case FIMD_VIDOSD_START ... FIMD_VIDOSD_END: + w = (offset - FIMD_VIDOSD_START) >> 4; + i = ((offset - FIMD_VIDOSD_START) & 0xF) >> 2; + switch (i) { + case 0: + old_value = s->window[w].lefttop_y; + s->window[w].lefttop_x = (val >> FIMD_VIDOSD_HOR_SHIFT) & + FIMD_VIDOSD_COORD_MASK; + s->window[w].lefttop_y = (val >> FIMD_VIDOSD_VER_SHIFT) & + FIMD_VIDOSD_COORD_MASK; + if (s->window[w].lefttop_y != old_value) { + fimd_update_memory_section(s, w); + } + break; + case 1: + old_value = s->window[w].rightbot_y; + s->window[w].rightbot_x = (val >> FIMD_VIDOSD_HOR_SHIFT) & + FIMD_VIDOSD_COORD_MASK; + s->window[w].rightbot_y = (val >> FIMD_VIDOSD_VER_SHIFT) & + FIMD_VIDOSD_COORD_MASK; + if (s->window[w].rightbot_y != old_value) { + fimd_update_memory_section(s, w); + } + break; + case 2: + if (w == 0) { + s->window[w].osdsize = val; + } else { + s->window[w].alpha_val[0] = + unpack_upper_4((val & FIMD_VIDOSD_ALPHA_AEN0) >> + FIMD_VIDOSD_AEN0_SHIFT) | + (s->window[w].alpha_val[0] & FIMD_VIDALPHA_ALPHA_LOWER); + s->window[w].alpha_val[1] = + unpack_upper_4(val & FIMD_VIDOSD_ALPHA_AEN1) | + (s->window[w].alpha_val[1] & FIMD_VIDALPHA_ALPHA_LOWER); + } + break; + case 3: + if (w != 1 && w != 2) { + DPRINT_ERROR("Bad write offset 0x%08x\n", offset); + return; + } + s->window[w].osdsize = val; + break; + } + break; + case FIMD_VIDWADD0_START ... FIMD_VIDWADD0_END: + w = (offset - FIMD_VIDWADD0_START) >> 3; + i = ((offset - FIMD_VIDWADD0_START) >> 2) & 1; + if (i == fimd_get_buffer_id(&s->window[w]) && + s->window[w].buf_start[i] != val) { + s->window[w].buf_start[i] = val; + fimd_update_memory_section(s, w); + break; + } + s->window[w].buf_start[i] = val; + break; + case FIMD_VIDWADD1_START ... FIMD_VIDWADD1_END: + w = (offset - FIMD_VIDWADD1_START) >> 3; + i = ((offset - FIMD_VIDWADD1_START) >> 2) & 1; + s->window[w].buf_end[i] = val; + break; + case FIMD_VIDWADD2_START ... FIMD_VIDWADD2_END: + w = (offset - FIMD_VIDWADD2_START) >> 2; + if (((val & FIMD_VIDWADD2_PAGEWIDTH) != s->window[w].virtpage_width) || + (((val >> FIMD_VIDWADD2_OFFSIZE_SHIFT) & FIMD_VIDWADD2_OFFSIZE) != + s->window[w].virtpage_offsize)) { + s->window[w].virtpage_width = val & FIMD_VIDWADD2_PAGEWIDTH; + s->window[w].virtpage_offsize = + (val >> FIMD_VIDWADD2_OFFSIZE_SHIFT) & FIMD_VIDWADD2_OFFSIZE; + fimd_update_memory_section(s, w); + } + break; + case FIMD_VIDINTCON0: + s->vidintcon[0] = val; + break; + case FIMD_VIDINTCON1: + s->vidintcon[1] &= ~(val & 7); + exynos4210_fimd_update_irq(s); + break; + case FIMD_WKEYCON_START ... FIMD_WKEYCON_END: + w = ((offset - FIMD_WKEYCON_START) >> 3) + 1; + i = ((offset - FIMD_WKEYCON_START) >> 2) & 1; + s->window[w].keycon[i] = val; + break; + case FIMD_WKEYALPHA_START ... FIMD_WKEYALPHA_END: + w = ((offset - FIMD_WKEYALPHA_START) >> 2) + 1; + s->window[w].keyalpha = val; + break; + case FIMD_DITHMODE: + s->dithmode = val; + break; + case FIMD_WINMAP_START ... FIMD_WINMAP_END: + w = (offset - FIMD_WINMAP_START) >> 2; + old_value = s->window[w].winmap; + s->window[w].winmap = val; + if ((val & FIMD_WINMAP_EN) ^ (old_value & FIMD_WINMAP_EN)) { + exynos4210_fimd_invalidate(s); + exynos4210_fimd_update_win_bppmode(s, w); + exynos4210_fimd_trace_bppmode(s, w, 0xFFFFFFFF); + exynos4210_fimd_update(s); + } + break; + case FIMD_WPALCON_HIGH ... FIMD_WPALCON_LOW: + i = (offset - FIMD_WPALCON_HIGH) >> 2; + s->wpalcon[i] = val; + if (s->wpalcon[1] & FIMD_WPALCON_UPDATEEN) { + for (w = 0; w < NUM_OF_WINDOWS; w++) { + exynos4210_fimd_update_win_bppmode(s, w); + fimd_update_get_alpha(s, w); + } + } + break; + case FIMD_TRIGCON: + val = (val & ~FIMD_TRIGCON_ROMASK) | (s->trigcon & FIMD_TRIGCON_ROMASK); + s->trigcon = val; + break; + case FIMD_I80IFCON_START ... FIMD_I80IFCON_END: + s->i80ifcon[(offset - FIMD_I80IFCON_START) >> 2] = val; + break; + case FIMD_COLORGAINCON: + s->colorgaincon = val; + break; + case FIMD_LDI_CMDCON0 ... FIMD_LDI_CMDCON1: + s->ldi_cmdcon[(offset - FIMD_LDI_CMDCON0) >> 2] = val; + break; + case FIMD_SIFCCON0 ... FIMD_SIFCCON2: + i = (offset - FIMD_SIFCCON0) >> 2; + if (i != 2) { + s->sifccon[i] = val; + } + break; + case FIMD_HUECOEFCR_START ... FIMD_HUECOEFCR_END: + i = (offset - FIMD_HUECOEFCR_START) >> 2; + s->huecoef_cr[i] = val; + break; + case FIMD_HUECOEFCB_START ... FIMD_HUECOEFCB_END: + i = (offset - FIMD_HUECOEFCB_START) >> 2; + s->huecoef_cb[i] = val; + break; + case FIMD_HUEOFFSET: + s->hueoffset = val; + break; + case FIMD_VIDWALPHA_START ... FIMD_VIDWALPHA_END: + w = ((offset - FIMD_VIDWALPHA_START) >> 3); + i = ((offset - FIMD_VIDWALPHA_START) >> 2) & 1; + if (w == 0) { + s->window[w].alpha_val[i] = val; + } else { + s->window[w].alpha_val[i] = (val & FIMD_VIDALPHA_ALPHA_LOWER) | + (s->window[w].alpha_val[i] & FIMD_VIDALPHA_ALPHA_UPPER); + } + break; + case FIMD_BLENDEQ_START ... FIMD_BLENDEQ_END: + s->window[(offset - FIMD_BLENDEQ_START) >> 2].blendeq = val; + break; + case FIMD_BLENDCON: + old_value = s->blendcon; + s->blendcon = val; + if ((s->blendcon & FIMD_ALPHA_8BIT) != (old_value & FIMD_ALPHA_8BIT)) { + for (w = 0; w < NUM_OF_WINDOWS; w++) { + fimd_update_get_alpha(s, w); + } + } + break; + case FIMD_WRTQOSCON_START ... FIMD_WRTQOSCON_END: + s->window[(offset - FIMD_WRTQOSCON_START) >> 2].rtqoscon = val; + break; + case FIMD_I80IFCMD_START ... FIMD_I80IFCMD_END: + s->i80ifcmd[(offset - FIMD_I80IFCMD_START) >> 2] = val; + break; + case FIMD_VIDW0ADD0_B2 ... FIMD_VIDW4ADD0_B2: + if (offset & 0x0004) { + DPRINT_ERROR("bad write offset 0x%08x\n", offset); + break; + } + w = (offset - FIMD_VIDW0ADD0_B2) >> 3; + if (fimd_get_buffer_id(&s->window[w]) == 2 && + s->window[w].buf_start[2] != val) { + s->window[w].buf_start[2] = val; + fimd_update_memory_section(s, w); + break; + } + s->window[w].buf_start[2] = val; + break; + case FIMD_SHD_ADD0_START ... FIMD_SHD_ADD0_END: + if (offset & 0x0004) { + DPRINT_ERROR("bad write offset 0x%08x\n", offset); + break; + } + s->window[(offset - FIMD_SHD_ADD0_START) >> 3].shadow_buf_start = val; + break; + case FIMD_SHD_ADD1_START ... FIMD_SHD_ADD1_END: + if (offset & 0x0004) { + DPRINT_ERROR("bad write offset 0x%08x\n", offset); + break; + } + s->window[(offset - FIMD_SHD_ADD1_START) >> 3].shadow_buf_end = val; + break; + case FIMD_SHD_ADD2_START ... FIMD_SHD_ADD2_END: + s->window[(offset - FIMD_SHD_ADD2_START) >> 2].shadow_buf_size = val; + break; + case FIMD_PAL_MEM_START ... FIMD_PAL_MEM_END: + w = (offset - FIMD_PAL_MEM_START) >> 10; + i = ((offset - FIMD_PAL_MEM_START) >> 2) & 0xFF; + s->window[w].palette[i] = val; + break; + case FIMD_PALMEM_AL_START ... FIMD_PALMEM_AL_END: + /* Palette memory aliases for windows 0 and 1 */ + w = (offset - FIMD_PALMEM_AL_START) >> 10; + i = ((offset - FIMD_PALMEM_AL_START) >> 2) & 0xFF; + s->window[w].palette[i] = val; + break; + default: + DPRINT_ERROR("bad write offset 0x%08x\n", offset); + break; + } +} + +static uint64_t exynos4210_fimd_read(void *opaque, target_phys_addr_t offset, + unsigned size) +{ + Exynos4210fimdState *s = (Exynos4210fimdState *)opaque; + int w, i; + uint32_t ret = 0; + + DPRINT_L2("read offset 0x%08x\n", offset); + + switch (offset) { + case FIMD_VIDCON0 ... FIMD_VIDCON3: + return s->vidcon[(offset - FIMD_VIDCON0) >> 2]; + case FIMD_VIDTCON_START ... FIMD_VIDTCON_END: + return s->vidtcon[(offset - FIMD_VIDTCON_START) >> 2]; + case FIMD_WINCON_START ... FIMD_WINCON_END: + return s->window[(offset - FIMD_WINCON_START) >> 2].wincon; + case FIMD_SHADOWCON: + return s->shadowcon; + case FIMD_WINCHMAP: + return s->winchmap; + case FIMD_VIDOSD_START ... FIMD_VIDOSD_END: + w = (offset - FIMD_VIDOSD_START) >> 4; + i = ((offset - FIMD_VIDOSD_START) & 0xF) >> 2; + switch (i) { + case 0: + ret = ((s->window[w].lefttop_x & FIMD_VIDOSD_COORD_MASK) << + FIMD_VIDOSD_HOR_SHIFT) | + (s->window[w].lefttop_y & FIMD_VIDOSD_COORD_MASK); + break; + case 1: + ret = ((s->window[w].rightbot_x & FIMD_VIDOSD_COORD_MASK) << + FIMD_VIDOSD_HOR_SHIFT) | + (s->window[w].rightbot_y & FIMD_VIDOSD_COORD_MASK); + break; + case 2: + if (w == 0) { + ret = s->window[w].osdsize; + } else { + ret = (pack_upper_4(s->window[w].alpha_val[0]) << + FIMD_VIDOSD_AEN0_SHIFT) | + pack_upper_4(s->window[w].alpha_val[1]); + } + break; + case 3: + if (w != 1 && w != 2) { + DPRINT_ERROR("bad read offset 0x%08x\n", offset); + return 0xBAADBAAD; + } + ret = s->window[w].osdsize; + break; + } + return ret; + case FIMD_VIDWADD0_START ... FIMD_VIDWADD0_END: + w = (offset - FIMD_VIDWADD0_START) >> 3; + i = ((offset - FIMD_VIDWADD0_START) >> 2) & 1; + return s->window[w].buf_start[i]; + case FIMD_VIDWADD1_START ... FIMD_VIDWADD1_END: + w = (offset - FIMD_VIDWADD1_START) >> 3; + i = ((offset - FIMD_VIDWADD1_START) >> 2) & 1; + return s->window[w].buf_end[i]; + case FIMD_VIDWADD2_START ... FIMD_VIDWADD2_END: + w = (offset - FIMD_VIDWADD2_START) >> 2; + return s->window[w].virtpage_width | (s->window[w].virtpage_offsize << + FIMD_VIDWADD2_OFFSIZE_SHIFT); + case FIMD_VIDINTCON0 ... FIMD_VIDINTCON1: + return s->vidintcon[(offset - FIMD_VIDINTCON0) >> 2]; + case FIMD_WKEYCON_START ... FIMD_WKEYCON_END: + w = ((offset - FIMD_WKEYCON_START) >> 3) + 1; + i = ((offset - FIMD_WKEYCON_START) >> 2) & 1; + return s->window[w].keycon[i]; + case FIMD_WKEYALPHA_START ... FIMD_WKEYALPHA_END: + w = ((offset - FIMD_WKEYALPHA_START) >> 2) + 1; + return s->window[w].keyalpha; + case FIMD_DITHMODE: + return s->dithmode; + case FIMD_WINMAP_START ... FIMD_WINMAP_END: + return s->window[(offset - FIMD_WINMAP_START) >> 2].winmap; + case FIMD_WPALCON_HIGH ... FIMD_WPALCON_LOW: + return s->wpalcon[(offset - FIMD_WPALCON_HIGH) >> 2]; + case FIMD_TRIGCON: + return s->trigcon; + case FIMD_I80IFCON_START ... FIMD_I80IFCON_END: + return s->i80ifcon[(offset - FIMD_I80IFCON_START) >> 2]; + case FIMD_COLORGAINCON: + return s->colorgaincon; + case FIMD_LDI_CMDCON0 ... FIMD_LDI_CMDCON1: + return s->ldi_cmdcon[(offset - FIMD_LDI_CMDCON0) >> 2]; + case FIMD_SIFCCON0 ... FIMD_SIFCCON2: + i = (offset - FIMD_SIFCCON0) >> 2; + return s->sifccon[i]; + case FIMD_HUECOEFCR_START ... FIMD_HUECOEFCR_END: + i = (offset - FIMD_HUECOEFCR_START) >> 2; + return s->huecoef_cr[i]; + case FIMD_HUECOEFCB_START ... FIMD_HUECOEFCB_END: + i = (offset - FIMD_HUECOEFCB_START) >> 2; + return s->huecoef_cb[i]; + case FIMD_HUEOFFSET: + return s->hueoffset; + case FIMD_VIDWALPHA_START ... FIMD_VIDWALPHA_END: + w = ((offset - FIMD_VIDWALPHA_START) >> 3); + i = ((offset - FIMD_VIDWALPHA_START) >> 2) & 1; + return s->window[w].alpha_val[i] & + (w == 0 ? 0xFFFFFF : FIMD_VIDALPHA_ALPHA_LOWER); + case FIMD_BLENDEQ_START ... FIMD_BLENDEQ_END: + return s->window[(offset - FIMD_BLENDEQ_START) >> 2].blendeq; + case FIMD_BLENDCON: + return s->blendcon; + case FIMD_WRTQOSCON_START ... FIMD_WRTQOSCON_END: + return s->window[(offset - FIMD_WRTQOSCON_START) >> 2].rtqoscon; + case FIMD_I80IFCMD_START ... FIMD_I80IFCMD_END: + return s->i80ifcmd[(offset - FIMD_I80IFCMD_START) >> 2]; + case FIMD_VIDW0ADD0_B2 ... FIMD_VIDW4ADD0_B2: + if (offset & 0x0004) { + break; + } + return s->window[(offset - FIMD_VIDW0ADD0_B2) >> 3].buf_start[2]; + case FIMD_SHD_ADD0_START ... FIMD_SHD_ADD0_END: + if (offset & 0x0004) { + break; + } + return s->window[(offset - FIMD_SHD_ADD0_START) >> 3].shadow_buf_start; + case FIMD_SHD_ADD1_START ... FIMD_SHD_ADD1_END: + if (offset & 0x0004) { + break; + } + return s->window[(offset - FIMD_SHD_ADD1_START) >> 3].shadow_buf_end; + case FIMD_SHD_ADD2_START ... FIMD_SHD_ADD2_END: + return s->window[(offset - FIMD_SHD_ADD2_START) >> 2].shadow_buf_size; + case FIMD_PAL_MEM_START ... FIMD_PAL_MEM_END: + w = (offset - FIMD_PAL_MEM_START) >> 10; + i = ((offset - FIMD_PAL_MEM_START) >> 2) & 0xFF; + return s->window[w].palette[i]; + case FIMD_PALMEM_AL_START ... FIMD_PALMEM_AL_END: + /* Palette aliases for win 0,1 */ + w = (offset - FIMD_PALMEM_AL_START) >> 10; + i = ((offset - FIMD_PALMEM_AL_START) >> 2) & 0xFF; + return s->window[w].palette[i]; + } + + DPRINT_ERROR("bad read offset 0x%08x\n", offset); + return 0xBAADBAAD; +} + +static const MemoryRegionOps exynos4210_fimd_mmio_ops = { + .read = exynos4210_fimd_read, + .write = exynos4210_fimd_write, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int exynos4210_fimd_load(void *opaque, int version_id) +{ + Exynos4210fimdState *s = (Exynos4210fimdState *)opaque; + int w; + + if (version_id != 1) { + return -EINVAL; + } + + for (w = 0; w < NUM_OF_WINDOWS; w++) { + exynos4210_fimd_update_win_bppmode(s, w); + fimd_update_get_alpha(s, w); + fimd_update_memory_section(s, w); + } + + /* Redraw the whole screen */ + exynos4210_update_resolution(s); + exynos4210_fimd_invalidate(s); + exynos4210_fimd_enable(s, (s->vidcon[0] & FIMD_VIDCON0_ENVID_MASK) == + FIMD_VIDCON0_ENVID_MASK); + return 0; +} + +static const VMStateDescription exynos4210_fimd_window_vmstate = { + .name = "exynos4210.fimd_window", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(wincon, Exynos4210fimdWindow), + VMSTATE_UINT32_ARRAY(buf_start, Exynos4210fimdWindow, 3), + VMSTATE_UINT32_ARRAY(buf_end, Exynos4210fimdWindow, 3), + VMSTATE_UINT32_ARRAY(keycon, Exynos4210fimdWindow, 2), + VMSTATE_UINT32(keyalpha, Exynos4210fimdWindow), + VMSTATE_UINT32(winmap, Exynos4210fimdWindow), + VMSTATE_UINT32(blendeq, Exynos4210fimdWindow), + VMSTATE_UINT32(rtqoscon, Exynos4210fimdWindow), + VMSTATE_UINT32_ARRAY(palette, Exynos4210fimdWindow, 256), + VMSTATE_UINT32(shadow_buf_start, Exynos4210fimdWindow), + VMSTATE_UINT32(shadow_buf_end, Exynos4210fimdWindow), + VMSTATE_UINT32(shadow_buf_size, Exynos4210fimdWindow), + VMSTATE_UINT16(lefttop_x, Exynos4210fimdWindow), + VMSTATE_UINT16(lefttop_y, Exynos4210fimdWindow), + VMSTATE_UINT16(rightbot_x, Exynos4210fimdWindow), + VMSTATE_UINT16(rightbot_y, Exynos4210fimdWindow), + VMSTATE_UINT32(osdsize, Exynos4210fimdWindow), + VMSTATE_UINT32_ARRAY(alpha_val, Exynos4210fimdWindow, 2), + VMSTATE_UINT16(virtpage_width, Exynos4210fimdWindow), + VMSTATE_UINT16(virtpage_offsize, Exynos4210fimdWindow), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription exynos4210_fimd_vmstate = { + .name = "exynos4210.fimd", + .version_id = 1, + .minimum_version_id = 1, + .post_load = exynos4210_fimd_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(vidcon, Exynos4210fimdState, 4), + VMSTATE_UINT32_ARRAY(vidtcon, Exynos4210fimdState, 4), + VMSTATE_UINT32(shadowcon, Exynos4210fimdState), + VMSTATE_UINT32(winchmap, Exynos4210fimdState), + VMSTATE_UINT32_ARRAY(vidintcon, Exynos4210fimdState, 2), + VMSTATE_UINT32(dithmode, Exynos4210fimdState), + VMSTATE_UINT32_ARRAY(wpalcon, Exynos4210fimdState, 2), + VMSTATE_UINT32(trigcon, Exynos4210fimdState), + VMSTATE_UINT32_ARRAY(i80ifcon, Exynos4210fimdState, 4), + VMSTATE_UINT32(colorgaincon, Exynos4210fimdState), + VMSTATE_UINT32_ARRAY(ldi_cmdcon, Exynos4210fimdState, 2), + VMSTATE_UINT32_ARRAY(sifccon, Exynos4210fimdState, 3), + VMSTATE_UINT32_ARRAY(huecoef_cr, Exynos4210fimdState, 4), + VMSTATE_UINT32_ARRAY(huecoef_cb, Exynos4210fimdState, 4), + VMSTATE_UINT32(hueoffset, Exynos4210fimdState), + VMSTATE_UINT32_ARRAY(i80ifcmd, Exynos4210fimdState, 12), + VMSTATE_UINT32(blendcon, Exynos4210fimdState), + VMSTATE_STRUCT_ARRAY(window, Exynos4210fimdState, 5, 1, + exynos4210_fimd_window_vmstate, Exynos4210fimdWindow), + VMSTATE_END_OF_LIST() + } +}; + +static int exynos4210_fimd_init(SysBusDevice *dev) +{ + Exynos4210fimdState *s = FROM_SYSBUS(Exynos4210fimdState, dev); + + s->ifb = NULL; + + sysbus_init_irq(dev, &s->irq[0]); + sysbus_init_irq(dev, &s->irq[1]); + sysbus_init_irq(dev, &s->irq[2]); + + memory_region_init_io(&s->iomem, &exynos4210_fimd_mmio_ops, s, + "exynos4210.fimd", FIMD_REGS_SIZE); + sysbus_init_mmio(dev, &s->iomem); + s->console = graphic_console_init(exynos4210_fimd_update, + exynos4210_fimd_invalidate, NULL, NULL, s); + + return 0; +} + +static void exynos4210_fimd_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + dc->vmsd = &exynos4210_fimd_vmstate; + dc->reset = exynos4210_fimd_reset; + k->init = exynos4210_fimd_init; +} + +static TypeInfo exynos4210_fimd_info = { + .name = "exynos4210.fimd", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Exynos4210fimdState), + .class_init = exynos4210_fimd_class_init, +}; + +static void exynos4210_fimd_register_types(void) +{ + type_register_static(&exynos4210_fimd_info); +} + +type_init(exynos4210_fimd_register_types) -- cgit v1.1 From 5d782e0805b4d216909b21883dcd513c3061aa3d Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 16 Feb 2012 09:56:07 +0000 Subject: hw/a15mpcore.c: Add Cortex-A15 private peripheral model Add a model of the Cortex-A15 memory mapped private peripheral space. This is fairly simple because the only memory mapped bit of the A15 is the GIC. Note that we don't currently model a VGIC and therefore don't map the VGIC related bits of the GIC. Signed-off-by: Peter Maydell --- hw/a15mpcore.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 hw/a15mpcore.c (limited to 'hw') diff --git a/hw/a15mpcore.c b/hw/a15mpcore.c new file mode 100644 index 0000000..71142e5 --- /dev/null +++ b/hw/a15mpcore.c @@ -0,0 +1,103 @@ +/* + * Cortex-A15MPCore internal peripheral emulation. + * + * Copyright (c) 2012 Linaro Limited. + * Written by Peter Maydell. + * + * 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 . + */ + +#include "sysbus.h" + +/* Configuration for arm_gic.c: + * max number of CPUs, how to ID current CPU + */ +#define NCPU 4 + +static inline int gic_get_current_cpu(void) +{ + return cpu_single_env->cpu_index; +} + +#include "arm_gic.c" + +/* A15MP private memory region. */ + +typedef struct A15MPPrivState { + gic_state gic; + uint32_t num_cpu; + uint32_t num_irq; + MemoryRegion container; +} A15MPPrivState; + +static int a15mp_priv_init(SysBusDevice *dev) +{ + A15MPPrivState *s = FROM_SYSBUSGIC(A15MPPrivState, dev); + + if (s->num_cpu > NCPU) { + hw_error("a15mp_priv_init: num-cpu may not be more than %d\n", NCPU); + } + + gic_init(&s->gic, s->num_cpu, s->num_irq); + + /* Memory map (addresses are offsets from PERIPHBASE): + * 0x0000-0x0fff -- reserved + * 0x1000-0x1fff -- GIC Distributor + * 0x2000-0x2fff -- GIC CPU interface + * 0x4000-0x4fff -- GIC virtual interface control (not modelled) + * 0x5000-0x5fff -- GIC virtual interface control (not modelled) + * 0x6000-0x7fff -- GIC virtual CPU interface (not modelled) + */ + memory_region_init(&s->container, "a15mp-priv-container", 0x8000); + memory_region_add_subregion(&s->container, 0x1000, &s->gic.iomem); + memory_region_add_subregion(&s->container, 0x2000, &s->gic.cpuiomem[0]); + + sysbus_init_mmio(dev, &s->container); + return 0; +} + +static Property a15mp_priv_properties[] = { + DEFINE_PROP_UINT32("num-cpu", A15MPPrivState, num_cpu, 1), + /* The Cortex-A15MP may have anything from 0 to 224 external interrupt + * IRQ lines (with another 32 internal). We default to 64+32, which + * is the number provided by the Cortex-A15MP test chip in the + * Versatile Express A15 development board. + * Other boards may differ and should set this property appropriately. + */ + DEFINE_PROP_UINT32("num-irq", A15MPPrivState, num_irq, 96), + DEFINE_PROP_END_OF_LIST(), +}; + +static void a15mp_priv_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + k->init = a15mp_priv_init; + dc->props = a15mp_priv_properties; + /* We currently have no savable state outside the common GIC state */ +} + +static TypeInfo a15mp_priv_info = { + .name = "a15mpcore_priv", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(A15MPPrivState), + .class_init = a15mp_priv_class_init, +}; + +static void a15mp_register_types(void) +{ + type_register_static(&a15mp_priv_info); +} + +type_init(a15mp_register_types) -- cgit v1.1 From 2558e0a67b5573cfd29c9346350ac5d5dc6f866f Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 16 Feb 2012 09:56:08 +0000 Subject: hw/vexpress.c: Make motherboard peripheral memory map table-driven MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pull the addresses used for mapping motherboard peripherals into memory out into a table. This will allow us to simply provide a second table to implement the "Cortex-A Series" memory map used by the A15 variant of Versatile Express, as well as the current "Legacy" map used by A9. Signed-off-by: Peter Maydell Reviewed-by: Andreas Färber --- hw/vexpress.c | 137 +++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 103 insertions(+), 34 deletions(-) (limited to 'hw') diff --git a/hw/vexpress.c b/hw/vexpress.c index 43f47a6..38ae05f 100644 --- a/hw/vexpress.c +++ b/hw/vexpress.c @@ -31,13 +31,80 @@ #include "exec-memory.h" #define SMP_BOOT_ADDR 0xe0000000 -#define SMP_BOOTREG_ADDR 0x10000030 #define VEXPRESS_BOARD_ID 0x8e0 static struct arm_boot_info vexpress_binfo = { .smp_loader_start = SMP_BOOT_ADDR, - .smp_bootreg_addr = SMP_BOOTREG_ADDR, +}; + +/* Address maps for peripherals: + * the Versatile Express motherboard has two possible maps, + * the "legacy" one (used for A9) and the "Cortex-A Series" + * map (used for newer cores). + * Individual daughterboards can also have different maps for + * their peripherals. + */ + +enum { + VE_SYSREGS, + VE_SP810, + VE_SERIALPCI, + VE_PL041, + VE_MMCI, + VE_KMI0, + VE_KMI1, + VE_UART0, + VE_UART1, + VE_UART2, + VE_UART3, + VE_WDT, + VE_TIMER01, + VE_TIMER23, + VE_SERIALDVI, + VE_RTC, + VE_COMPACTFLASH, + VE_CLCD, + VE_NORFLASH0, + VE_NORFLASH0ALIAS, + VE_NORFLASH1, + VE_SRAM, + VE_VIDEORAM, + VE_ETHERNET, + VE_USB, + VE_DAPROM, +}; + +static target_phys_addr_t motherboard_legacy_map[] = { + /* CS7: 0x10000000 .. 0x10020000 */ + [VE_SYSREGS] = 0x10000000, + [VE_SP810] = 0x10001000, + [VE_SERIALPCI] = 0x10002000, + [VE_PL041] = 0x10004000, + [VE_MMCI] = 0x10005000, + [VE_KMI0] = 0x10006000, + [VE_KMI1] = 0x10007000, + [VE_UART0] = 0x10009000, + [VE_UART1] = 0x1000a000, + [VE_UART2] = 0x1000b000, + [VE_UART3] = 0x1000c000, + [VE_WDT] = 0x1000f000, + [VE_TIMER01] = 0x10011000, + [VE_TIMER23] = 0x10012000, + [VE_SERIALDVI] = 0x10016000, + [VE_RTC] = 0x10017000, + [VE_COMPACTFLASH] = 0x1001a000, + [VE_CLCD] = 0x1001f000, + /* CS0: 0x40000000 .. 0x44000000 */ + [VE_NORFLASH0] = 0x40000000, + /* CS1: 0x44000000 .. 0x48000000 */ + [VE_NORFLASH1] = 0x44000000, + /* CS2: 0x48000000 .. 0x4a000000 */ + [VE_SRAM] = 0x48000000, + /* CS3: 0x4c000000 .. 0x50000000 */ + [VE_VIDEORAM] = 0x4c000000, + [VE_ETHERNET] = 0x4e000000, + [VE_USB] = 0x4f000000, }; static void vexpress_a9_init(ram_addr_t ram_size, @@ -61,6 +128,7 @@ static void vexpress_a9_init(ram_addr_t ram_size, uint32_t proc_id; uint32_t sys_id; ram_addr_t low_ram_size, vram_size, sram_size; + target_phys_addr_t *map = motherboard_legacy_map; if (!cpu_model) { cpu_model = "cortex-a9"; @@ -116,53 +184,53 @@ static void vexpress_a9_init(ram_addr_t ram_size, pic[n] = qdev_get_gpio_in(dev, n); } - /* Motherboard peripherals CS7 : 0x10000000 .. 0x10020000 */ + /* Motherboard peripherals: the wiring is the same but the + * addresses vary between the legacy and A-Series memory maps. + */ + sys_id = 0x1190f500; proc_id = 0x0c000191; - /* 0x10000000 System registers */ sysctl = qdev_create(NULL, "realview_sysctl"); qdev_prop_set_uint32(sysctl, "sys_id", sys_id); qdev_prop_set_uint32(sysctl, "proc_id", proc_id); qdev_init_nofail(sysctl); - sysbus_mmio_map(sysbus_from_qdev(sysctl), 0, 0x10000000); + sysbus_mmio_map(sysbus_from_qdev(sysctl), 0, map[VE_SYSREGS]); + + /* VE_SP810: not modelled */ + /* VE_SERIALPCI: not modelled */ - /* 0x10001000 SP810 system control */ - /* 0x10002000 serial bus PCI */ - /* 0x10004000 PL041 audio */ pl041 = qdev_create(NULL, "pl041"); qdev_prop_set_uint32(pl041, "nc_fifo_depth", 512); qdev_init_nofail(pl041); - sysbus_mmio_map(sysbus_from_qdev(pl041), 0, 0x10004000); + sysbus_mmio_map(sysbus_from_qdev(pl041), 0, map[VE_PL041]); sysbus_connect_irq(sysbus_from_qdev(pl041), 0, pic[11]); - dev = sysbus_create_varargs("pl181", 0x10005000, pic[9], pic[10], NULL); + dev = sysbus_create_varargs("pl181", map[VE_MMCI], pic[9], pic[10], NULL); /* Wire up MMC card detect and read-only signals */ qdev_connect_gpio_out(dev, 0, qdev_get_gpio_in(sysctl, ARM_SYSCTL_GPIO_MMC_WPROT)); qdev_connect_gpio_out(dev, 1, qdev_get_gpio_in(sysctl, ARM_SYSCTL_GPIO_MMC_CARDIN)); - sysbus_create_simple("pl050_keyboard", 0x10006000, pic[12]); - sysbus_create_simple("pl050_mouse", 0x10007000, pic[13]); - - sysbus_create_simple("pl011", 0x10009000, pic[5]); - sysbus_create_simple("pl011", 0x1000a000, pic[6]); - sysbus_create_simple("pl011", 0x1000b000, pic[7]); - sysbus_create_simple("pl011", 0x1000c000, pic[8]); + sysbus_create_simple("pl050_keyboard", map[VE_KMI0], pic[12]); + sysbus_create_simple("pl050_mouse", map[VE_KMI1], pic[13]); - /* 0x1000f000 SP805 WDT */ + sysbus_create_simple("pl011", map[VE_UART0], pic[5]); + sysbus_create_simple("pl011", map[VE_UART1], pic[6]); + sysbus_create_simple("pl011", map[VE_UART2], pic[7]); + sysbus_create_simple("pl011", map[VE_UART3], pic[8]); - sysbus_create_simple("sp804", 0x10011000, pic[2]); - sysbus_create_simple("sp804", 0x10012000, pic[3]); + sysbus_create_simple("sp804", map[VE_TIMER01], pic[2]); + sysbus_create_simple("sp804", map[VE_TIMER23], pic[3]); - /* 0x10016000 Serial Bus DVI */ + /* VE_SERIALDVI: not modelled */ - sysbus_create_simple("pl031", 0x10017000, pic[4]); /* RTC */ + sysbus_create_simple("pl031", map[VE_RTC], pic[4]); /* RTC */ - /* 0x1001a000 Compact Flash */ + /* VE_COMPACTFLASH: not modelled */ - /* 0x1001f000 PL111 CLCD (motherboard) */ + /* VE_CLCD: not modelled (we use the daughterboard CLCD only) */ /* Daughterboard peripherals : 0x10020000 .. 0x20000000 */ @@ -184,28 +252,28 @@ static void vexpress_a9_init(ram_addr_t ram_size, /* 0x1e00a000 PL310 L2 Cache Controller */ sysbus_create_varargs("l2x0", 0x1e00a000, NULL); - /* CS0: NOR0 flash : 0x40000000 .. 0x44000000 */ - /* CS4: NOR1 flash : 0x44000000 .. 0x48000000 */ - /* CS2: SRAM : 0x48000000 .. 0x4a000000 */ + /* VE_NORFLASH0: not modelled */ + /* VE_NORFLASH0ALIAS: not modelled */ + /* VE_NORFLASH1: not modelled */ + sram_size = 0x2000000; memory_region_init_ram(sram, "vexpress.sram", sram_size); vmstate_register_ram_global(sram); - memory_region_add_subregion(sysmem, 0x48000000, sram); - - /* CS3: USB, ethernet, VRAM : 0x4c000000 .. 0x50000000 */ + memory_region_add_subregion(sysmem, map[VE_SRAM], sram); - /* 0x4c000000 Video RAM */ vram_size = 0x800000; memory_region_init_ram(vram, "vexpress.vram", vram_size); vmstate_register_ram_global(vram); - memory_region_add_subregion(sysmem, 0x4c000000, vram); + memory_region_add_subregion(sysmem, map[VE_VIDEORAM], vram); /* 0x4e000000 LAN9118 Ethernet */ if (nd_table[0].vlan) { - lan9118_init(&nd_table[0], 0x4e000000, pic[15]); + lan9118_init(&nd_table[0], map[VE_ETHERNET], pic[15]); } - /* 0x4f000000 ISP1761 USB */ + /* VE_USB: not modelled */ + + /* VE_DAPROM: not modelled */ /* ??? Hack to map an additional page of ram for the secondary CPU startup code. I guess this works on real hardware because the @@ -222,6 +290,7 @@ static void vexpress_a9_init(ram_addr_t ram_size, vexpress_binfo.nb_cpus = smp_cpus; vexpress_binfo.board_id = VEXPRESS_BOARD_ID; vexpress_binfo.loader_start = 0x60000000; + vexpress_binfo.smp_bootreg_addr = map[VE_SYSREGS] + 0x30; arm_load_kernel(first_cpu, &vexpress_binfo); } -- cgit v1.1 From aac1e02c1d4b6d02f35a199a454fd46416380ff5 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 16 Feb 2012 09:56:08 +0000 Subject: hw/vexpress.c: Move secondary CPU boot code to SRAM On real Versatile Express hardware, the boot ROM puts the secondary CPU bootcode/holding pen in SRAM. We can therefore rely on Linux not trashing this memory until secondary CPUs have booted up, and can put our QEMU-specific pen code in the same place. This allows us to drop the odd "hack" RAM page we were using before. Signed-off-by: Peter Maydell --- hw/vexpress.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) (limited to 'hw') diff --git a/hw/vexpress.c b/hw/vexpress.c index 38ae05f..6d8eee2 100644 --- a/hw/vexpress.c +++ b/hw/vexpress.c @@ -30,13 +30,9 @@ #include "boards.h" #include "exec-memory.h" -#define SMP_BOOT_ADDR 0xe0000000 - #define VEXPRESS_BOARD_ID 0x8e0 -static struct arm_boot_info vexpress_binfo = { - .smp_loader_start = SMP_BOOT_ADDR, -}; +static struct arm_boot_info vexpress_binfo; /* Address maps for peripherals: * the Versatile Express motherboard has two possible maps, @@ -118,7 +114,6 @@ static void vexpress_a9_init(ram_addr_t ram_size, MemoryRegion *lowram = g_new(MemoryRegion, 1); MemoryRegion *vram = g_new(MemoryRegion, 1); MemoryRegion *sram = g_new(MemoryRegion, 1); - MemoryRegion *hackram = g_new(MemoryRegion, 1); DeviceState *dev, *sysctl, *pl041; SysBusDevice *busdev; qemu_irq *irqp; @@ -275,14 +270,6 @@ static void vexpress_a9_init(ram_addr_t ram_size, /* VE_DAPROM: not modelled */ - /* ??? Hack to map an additional page of ram for the secondary CPU - startup code. I guess this works on real hardware because the - BootROM happens to be in ROM/flash or in memory that isn't clobbered - until after Linux boots the secondary CPUs. */ - memory_region_init_ram(hackram, "vexpress.hack", 0x1000); - vmstate_register_ram_global(hackram); - memory_region_add_subregion(sysmem, SMP_BOOT_ADDR, hackram); - vexpress_binfo.ram_size = ram_size; vexpress_binfo.kernel_filename = kernel_filename; vexpress_binfo.kernel_cmdline = kernel_cmdline; @@ -290,6 +277,7 @@ static void vexpress_a9_init(ram_addr_t ram_size, vexpress_binfo.nb_cpus = smp_cpus; vexpress_binfo.board_id = VEXPRESS_BOARD_ID; vexpress_binfo.loader_start = 0x60000000; + vexpress_binfo.smp_loader_start = map[VE_SRAM]; vexpress_binfo.smp_bootreg_addr = map[VE_SYSREGS] + 0x30; arm_load_kernel(first_cpu, &vexpress_binfo); } -- cgit v1.1 From 4c3b29b8ad22857b1bb8e28cb17eabd108d9b978 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 16 Feb 2012 09:56:09 +0000 Subject: hw/vexpress.c: Factor out daughterboard-specific initialization Factor out daughterboard specifics into a data structure and daughterboard initialization function, in preparation for adding vexpress-a15 support. Signed-off-by: Peter Maydell --- hw/vexpress.c | 118 +++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 83 insertions(+), 35 deletions(-) (limited to 'hw') diff --git a/hw/vexpress.c b/hw/vexpress.c index 6d8eee2..8239ea1 100644 --- a/hw/vexpress.c +++ b/hw/vexpress.c @@ -103,32 +103,43 @@ static target_phys_addr_t motherboard_legacy_map[] = { [VE_USB] = 0x4f000000, }; -static void vexpress_a9_init(ram_addr_t ram_size, - const char *boot_device, - const char *kernel_filename, const char *kernel_cmdline, - const char *initrd_filename, const char *cpu_model) +/* Structure defining the peculiarities of a specific daughterboard */ + +typedef struct VEDBoardInfo VEDBoardInfo; + +typedef void DBoardInitFn(const VEDBoardInfo *daughterboard, + ram_addr_t ram_size, + const char *cpu_model, + qemu_irq *pic, uint32_t *proc_id); + +struct VEDBoardInfo { + const target_phys_addr_t *motherboard_map; + target_phys_addr_t loader_start; + DBoardInitFn *init; +}; + +static void a9_daughterboard_init(const VEDBoardInfo *daughterboard, + ram_addr_t ram_size, + const char *cpu_model, + qemu_irq *pic, uint32_t *proc_id) { CPUState *env = NULL; MemoryRegion *sysmem = get_system_memory(); MemoryRegion *ram = g_new(MemoryRegion, 1); MemoryRegion *lowram = g_new(MemoryRegion, 1); - MemoryRegion *vram = g_new(MemoryRegion, 1); - MemoryRegion *sram = g_new(MemoryRegion, 1); - DeviceState *dev, *sysctl, *pl041; + DeviceState *dev; SysBusDevice *busdev; qemu_irq *irqp; - qemu_irq pic[64]; int n; qemu_irq cpu_irq[4]; - uint32_t proc_id; - uint32_t sys_id; - ram_addr_t low_ram_size, vram_size, sram_size; - target_phys_addr_t *map = motherboard_legacy_map; + ram_addr_t low_ram_size; if (!cpu_model) { cpu_model = "cortex-a9"; } + *proc_id = 0x0c000191; + for (n = 0; n < smp_cpus; n++) { env = cpu_init(cpu_model); if (!env) { @@ -141,7 +152,7 @@ static void vexpress_a9_init(ram_addr_t ram_size, if (ram_size > 0x40000000) { /* 1GB is the maximum the address space permits */ - fprintf(stderr, "vexpress: cannot model more than 1GB RAM\n"); + fprintf(stderr, "vexpress-a9: cannot model more than 1GB RAM\n"); exit(1); } @@ -179,12 +190,58 @@ static void vexpress_a9_init(ram_addr_t ram_size, pic[n] = qdev_get_gpio_in(dev, n); } + /* Daughterboard peripherals : 0x10020000 .. 0x20000000 */ + + /* 0x10020000 PL111 CLCD (daughterboard) */ + sysbus_create_simple("pl111", 0x10020000, pic[44]); + + /* 0x10060000 AXI RAM */ + /* 0x100e0000 PL341 Dynamic Memory Controller */ + /* 0x100e1000 PL354 Static Memory Controller */ + /* 0x100e2000 System Configuration Controller */ + + sysbus_create_simple("sp804", 0x100e4000, pic[48]); + /* 0x100e5000 SP805 Watchdog module */ + /* 0x100e6000 BP147 TrustZone Protection Controller */ + /* 0x100e9000 PL301 'Fast' AXI matrix */ + /* 0x100ea000 PL301 'Slow' AXI matrix */ + /* 0x100ec000 TrustZone Address Space Controller */ + /* 0x10200000 CoreSight debug APB */ + /* 0x1e00a000 PL310 L2 Cache Controller */ + sysbus_create_varargs("l2x0", 0x1e00a000, NULL); +} + +static const VEDBoardInfo a9_daughterboard = { + .motherboard_map = motherboard_legacy_map, + .loader_start = 0x60000000, + .init = a9_daughterboard_init, +}; + +static void vexpress_common_init(const VEDBoardInfo *daughterboard, + ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model) +{ + DeviceState *dev, *sysctl, *pl041; + qemu_irq pic[64]; + uint32_t proc_id; + uint32_t sys_id; + ram_addr_t vram_size, sram_size; + MemoryRegion *sysmem = get_system_memory(); + MemoryRegion *vram = g_new(MemoryRegion, 1); + MemoryRegion *sram = g_new(MemoryRegion, 1); + const target_phys_addr_t *map = daughterboard->motherboard_map; + + daughterboard->init(daughterboard, ram_size, cpu_model, pic, &proc_id); + /* Motherboard peripherals: the wiring is the same but the * addresses vary between the legacy and A-Series memory maps. */ sys_id = 0x1190f500; - proc_id = 0x0c000191; sysctl = qdev_create(NULL, "realview_sysctl"); qdev_prop_set_uint32(sysctl, "sys_id", sys_id); @@ -227,26 +284,6 @@ static void vexpress_a9_init(ram_addr_t ram_size, /* VE_CLCD: not modelled (we use the daughterboard CLCD only) */ - /* Daughterboard peripherals : 0x10020000 .. 0x20000000 */ - - /* 0x10020000 PL111 CLCD (daughterboard) */ - sysbus_create_simple("pl111", 0x10020000, pic[44]); - - /* 0x10060000 AXI RAM */ - /* 0x100e0000 PL341 Dynamic Memory Controller */ - /* 0x100e1000 PL354 Static Memory Controller */ - /* 0x100e2000 System Configuration Controller */ - - sysbus_create_simple("sp804", 0x100e4000, pic[48]); - /* 0x100e5000 SP805 Watchdog module */ - /* 0x100e6000 BP147 TrustZone Protection Controller */ - /* 0x100e9000 PL301 'Fast' AXI matrix */ - /* 0x100ea000 PL301 'Slow' AXI matrix */ - /* 0x100ec000 TrustZone Address Space Controller */ - /* 0x10200000 CoreSight debug APB */ - /* 0x1e00a000 PL310 L2 Cache Controller */ - sysbus_create_varargs("l2x0", 0x1e00a000, NULL); - /* VE_NORFLASH0: not modelled */ /* VE_NORFLASH0ALIAS: not modelled */ /* VE_NORFLASH1: not modelled */ @@ -276,12 +313,23 @@ static void vexpress_a9_init(ram_addr_t ram_size, vexpress_binfo.initrd_filename = initrd_filename; vexpress_binfo.nb_cpus = smp_cpus; vexpress_binfo.board_id = VEXPRESS_BOARD_ID; - vexpress_binfo.loader_start = 0x60000000; + vexpress_binfo.loader_start = daughterboard->loader_start; vexpress_binfo.smp_loader_start = map[VE_SRAM]; vexpress_binfo.smp_bootreg_addr = map[VE_SYSREGS] + 0x30; arm_load_kernel(first_cpu, &vexpress_binfo); } +static void vexpress_a9_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model) +{ + vexpress_common_init(&a9_daughterboard, + ram_size, boot_device, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model); +} static QEMUMachine vexpress_a9_machine = { .name = "vexpress-a9", -- cgit v1.1 From b7206878680f1e77031dacb420ef2c630a6b6257 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 16 Feb 2012 09:56:09 +0000 Subject: hw/vexpress.c: Instantiate the motherboard CLCD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instantiate the CLCD on the vexpress motherboard as well as one on the daughterboard -- the A15 daughterboard does not have a CLCD and so relies on the motherboard one. At the moment QEMU doesn't provide infrastructure for selecting which display device gets to actually show graphics -- the first one registered is it. Fortunately this works for the major use case (Linux): if the daughterboard has a CLCD it will come first and be used, otherwise we fall back to the motherboard CLCD. So we don't (currently) need to implement the control register which allows software to tell the mux which video output to pass through to the outside world. Signed-off-by: Peter Maydell Reviewed-by: Andreas Färber --- hw/vexpress.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'hw') diff --git a/hw/vexpress.c b/hw/vexpress.c index 8239ea1..43ad206 100644 --- a/hw/vexpress.c +++ b/hw/vexpress.c @@ -282,7 +282,7 @@ static void vexpress_common_init(const VEDBoardInfo *daughterboard, /* VE_COMPACTFLASH: not modelled */ - /* VE_CLCD: not modelled (we use the daughterboard CLCD only) */ + sysbus_create_simple("pl111", map[VE_CLCD], pic[14]); /* VE_NORFLASH0: not modelled */ /* VE_NORFLASH0ALIAS: not modelled */ -- cgit v1.1 From 96eacf641346bc1c432281575a265f6348a8f5c6 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 16 Feb 2012 09:56:09 +0000 Subject: arm_boot: Pass base address of GIC CPU interface, not whole GIC The arm_boot secondary boot loader code needs the address of the GIC CPU interface. Obtaining this from the base address of the private peripheral region was possible for A9 and 11MPcore, but the A15 puts the GIC CPU interface in a different place. So make boards pass in the GIC CPU interface address directly. Signed-off-by: Peter Maydell --- hw/arm-misc.h | 2 +- hw/arm_boot.c | 8 ++++---- hw/exynos4_boards.c | 3 ++- hw/realview.c | 12 +++++++----- hw/vexpress.c | 6 ++++-- 5 files changed, 18 insertions(+), 13 deletions(-) (limited to 'hw') diff --git a/hw/arm-misc.h b/hw/arm-misc.h index 5e5204b..306013a 100644 --- a/hw/arm-misc.h +++ b/hw/arm-misc.h @@ -37,7 +37,7 @@ struct arm_boot_info { */ target_phys_addr_t smp_loader_start; target_phys_addr_t smp_bootreg_addr; - target_phys_addr_t smp_priv_base; + target_phys_addr_t gic_cpu_if_addr; int nb_cpus; int board_id; int (*atag_board)(const struct arm_boot_info *info, void *p); diff --git a/hw/arm_boot.c b/hw/arm_boot.c index 34a8739..2ef25ca 100644 --- a/hw/arm_boot.c +++ b/hw/arm_boot.c @@ -43,16 +43,16 @@ static uint32_t bootloader[] = { * location for the kernel secondary CPU entry point. */ static uint32_t smpboot[] = { - 0xe59f201c, /* ldr r2, privbase */ + 0xe59f201c, /* ldr r2, gic_cpu_if */ 0xe59f001c, /* ldr r0, startaddr */ 0xe3a01001, /* mov r1, #1 */ - 0xe5821100, /* str r1, [r2, #256] */ + 0xe5821000, /* str r1, [r2] */ 0xe320f003, /* wfi */ 0xe5901000, /* ldr r1, [r0] */ 0xe1110001, /* tst r1, r1 */ 0x0afffffb, /* beq */ 0xe12fff11, /* bx r1 */ - 0, /* privbase: Private memory region base address. */ + 0, /* gic_cpu_if: base address of GIC CPU interface */ 0 /* bootreg: Boot register address is held here */ }; @@ -61,7 +61,7 @@ static void default_write_secondary(CPUState *env, { int n; smpboot[ARRAY_SIZE(smpboot) - 1] = info->smp_bootreg_addr; - smpboot[ARRAY_SIZE(smpboot) - 2] = info->smp_priv_base; + smpboot[ARRAY_SIZE(smpboot) - 2] = info->gic_cpu_if_addr; for (n = 0; n < ARRAY_SIZE(smpboot); n++) { smpboot[n] = tswap32(smpboot[n]); } diff --git a/hw/exynos4_boards.c b/hw/exynos4_boards.c index 329efbe..553a02b 100644 --- a/hw/exynos4_boards.c +++ b/hw/exynos4_boards.c @@ -112,7 +112,8 @@ static Exynos4210State *exynos4_boards_init_common( exynos4_board_binfo.kernel_filename = kernel_filename; exynos4_board_binfo.initrd_filename = initrd_filename; exynos4_board_binfo.kernel_cmdline = kernel_cmdline; - exynos4_board_binfo.smp_priv_base = EXYNOS4210_SMP_PRIVATE_BASE_ADDR; + exynos4_board_binfo.gic_cpu_if_addr = + EXYNOS4210_SMP_PRIVATE_BASE_ADDR + 0x100; PRINT_DEBUG("\n ram_size: %luMiB [0x%08lx]\n" " kernel_filename: %s\n" diff --git a/hw/realview.c b/hw/realview.c index bcf982f..ae1bbcd 100644 --- a/hw/realview.c +++ b/hw/realview.c @@ -222,21 +222,23 @@ static void realview_init(ram_addr_t ram_size, sysbus_mmio_map(sysbus_from_qdev(sysctl), 0, 0x10000000); if (is_mpcore) { + target_phys_addr_t periphbase; dev = qdev_create(NULL, is_pb ? "a9mpcore_priv": "realview_mpcore"); qdev_prop_set_uint32(dev, "num-cpu", smp_cpus); qdev_init_nofail(dev); busdev = sysbus_from_qdev(dev); if (is_pb) { - realview_binfo.smp_priv_base = 0x1f000000; + periphbase = 0x1f000000; } else { - realview_binfo.smp_priv_base = 0x10100000; + periphbase = 0x10100000; } - sysbus_mmio_map(busdev, 0, realview_binfo.smp_priv_base); + sysbus_mmio_map(busdev, 0, periphbase); for (n = 0; n < smp_cpus; n++) { sysbus_connect_irq(busdev, n, cpu_irq[n]); } - sysbus_create_varargs("l2x0", realview_binfo.smp_priv_base + 0x2000, - NULL); + sysbus_create_varargs("l2x0", periphbase + 0x2000, NULL); + /* Both A9 and 11MPCore put the GIC CPU i/f at base + 0x100 */ + realview_binfo.gic_cpu_if_addr = periphbase + 0x100; } else { uint32_t gic_addr = is_pb ? 0x1e000000 : 0x10040000; /* For now just create the nIRQ GIC, and ignore the others. */ diff --git a/hw/vexpress.c b/hw/vexpress.c index 43ad206..aae9d81 100644 --- a/hw/vexpress.c +++ b/hw/vexpress.c @@ -115,6 +115,7 @@ typedef void DBoardInitFn(const VEDBoardInfo *daughterboard, struct VEDBoardInfo { const target_phys_addr_t *motherboard_map; target_phys_addr_t loader_start; + const target_phys_addr_t gic_cpu_if_addr; DBoardInitFn *init; }; @@ -175,8 +176,7 @@ static void a9_daughterboard_init(const VEDBoardInfo *daughterboard, qdev_prop_set_uint32(dev, "num-cpu", smp_cpus); qdev_init_nofail(dev); busdev = sysbus_from_qdev(dev); - vexpress_binfo.smp_priv_base = 0x1e000000; - sysbus_mmio_map(busdev, 0, vexpress_binfo.smp_priv_base); + sysbus_mmio_map(busdev, 0, 0x1e000000); for (n = 0; n < smp_cpus; n++) { sysbus_connect_irq(busdev, n, cpu_irq[n]); } @@ -214,6 +214,7 @@ static void a9_daughterboard_init(const VEDBoardInfo *daughterboard, static const VEDBoardInfo a9_daughterboard = { .motherboard_map = motherboard_legacy_map, .loader_start = 0x60000000, + .gic_cpu_if_addr = 0x1e000100, .init = a9_daughterboard_init, }; @@ -316,6 +317,7 @@ static void vexpress_common_init(const VEDBoardInfo *daughterboard, vexpress_binfo.loader_start = daughterboard->loader_start; vexpress_binfo.smp_loader_start = map[VE_SRAM]; vexpress_binfo.smp_bootreg_addr = map[VE_SYSREGS] + 0x30; + vexpress_binfo.gic_cpu_if_addr = daughterboard->gic_cpu_if_addr; arm_load_kernel(first_cpu, &vexpress_binfo); } -- cgit v1.1 From 961f195e6c874de0473a23344be18d567d25dc39 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 16 Feb 2012 09:56:09 +0000 Subject: hw/vexpress.c: Add vexpress-a15 machine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the vexpress-a15 machine, and the A-Series memory map it uses. Signed-off-by: Peter Maydell Reviewed-by: Andreas Färber --- hw/vexpress.c | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) (limited to 'hw') diff --git a/hw/vexpress.c b/hw/vexpress.c index aae9d81..b9aafec 100644 --- a/hw/vexpress.c +++ b/hw/vexpress.c @@ -103,6 +103,41 @@ static target_phys_addr_t motherboard_legacy_map[] = { [VE_USB] = 0x4f000000, }; +static target_phys_addr_t motherboard_aseries_map[] = { + /* CS0: 0x00000000 .. 0x0c000000 */ + [VE_NORFLASH0] = 0x00000000, + [VE_NORFLASH0ALIAS] = 0x08000000, + /* CS4: 0x0c000000 .. 0x10000000 */ + [VE_NORFLASH1] = 0x0c000000, + /* CS5: 0x10000000 .. 0x14000000 */ + /* CS1: 0x14000000 .. 0x18000000 */ + [VE_SRAM] = 0x14000000, + /* CS2: 0x18000000 .. 0x1c000000 */ + [VE_VIDEORAM] = 0x18000000, + [VE_ETHERNET] = 0x1a000000, + [VE_USB] = 0x1b000000, + /* CS3: 0x1c000000 .. 0x20000000 */ + [VE_DAPROM] = 0x1c000000, + [VE_SYSREGS] = 0x1c010000, + [VE_SP810] = 0x1c020000, + [VE_SERIALPCI] = 0x1c030000, + [VE_PL041] = 0x1c040000, + [VE_MMCI] = 0x1c050000, + [VE_KMI0] = 0x1c060000, + [VE_KMI1] = 0x1c070000, + [VE_UART0] = 0x1c090000, + [VE_UART1] = 0x1c0a0000, + [VE_UART2] = 0x1c0b0000, + [VE_UART3] = 0x1c0c0000, + [VE_WDT] = 0x1c0f0000, + [VE_TIMER01] = 0x1c110000, + [VE_TIMER23] = 0x1c120000, + [VE_SERIALDVI] = 0x1c160000, + [VE_RTC] = 0x1c170000, + [VE_COMPACTFLASH] = 0x1c1a0000, + [VE_CLCD] = 0x1c1f0000, +}; + /* Structure defining the peculiarities of a specific daughterboard */ typedef struct VEDBoardInfo VEDBoardInfo; @@ -218,6 +253,91 @@ static const VEDBoardInfo a9_daughterboard = { .init = a9_daughterboard_init, }; +static void a15_daughterboard_init(const VEDBoardInfo *daughterboard, + ram_addr_t ram_size, + const char *cpu_model, + qemu_irq *pic, uint32_t *proc_id) +{ + int n; + CPUState *env = NULL; + MemoryRegion *sysmem = get_system_memory(); + MemoryRegion *ram = g_new(MemoryRegion, 1); + MemoryRegion *sram = g_new(MemoryRegion, 1); + qemu_irq cpu_irq[4]; + DeviceState *dev; + SysBusDevice *busdev; + + if (!cpu_model) { + cpu_model = "cortex-a15"; + } + + *proc_id = 0x14000217; + + for (n = 0; n < smp_cpus; n++) { + qemu_irq *irqp; + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + irqp = arm_pic_init_cpu(env); + cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ]; + } + + if (ram_size > 0x80000000) { + fprintf(stderr, "vexpress-a15: cannot model more than 2GB RAM\n"); + exit(1); + } + + memory_region_init_ram(ram, "vexpress.highmem", ram_size); + vmstate_register_ram_global(ram); + /* RAM is from 0x80000000 upwards; there is no low-memory alias for it. */ + memory_region_add_subregion(sysmem, 0x80000000, ram); + + /* 0x2c000000 A15MPCore private memory region (GIC) */ + dev = qdev_create(NULL, "a15mpcore_priv"); + qdev_prop_set_uint32(dev, "num-cpu", smp_cpus); + qdev_init_nofail(dev); + busdev = sysbus_from_qdev(dev); + sysbus_mmio_map(busdev, 0, 0x2c000000); + for (n = 0; n < smp_cpus; n++) { + sysbus_connect_irq(busdev, n, cpu_irq[n]); + } + /* Interrupts [42:0] are from the motherboard; + * [47:43] are reserved; [63:48] are daughterboard + * peripherals. Note that some documentation numbers + * external interrupts starting from 32 (because there + * are internal interrupts 0..31). + */ + for (n = 0; n < 64; n++) { + pic[n] = qdev_get_gpio_in(dev, n); + } + + /* A15 daughterboard peripherals: */ + + /* 0x20000000: CoreSight interfaces: not modelled */ + /* 0x2a000000: PL301 AXI interconnect: not modelled */ + /* 0x2a420000: SCC: not modelled */ + /* 0x2a430000: system counter: not modelled */ + /* 0x2b000000: HDLCD controller: not modelled */ + /* 0x2b060000: SP805 watchdog: not modelled */ + /* 0x2b0a0000: PL341 dynamic memory controller: not modelled */ + /* 0x2e000000: system SRAM */ + memory_region_init_ram(sram, "vexpress.a15sram", 0x10000); + vmstate_register_ram_global(sram); + memory_region_add_subregion(sysmem, 0x2e000000, sram); + + /* 0x7ffb0000: DMA330 DMA controller: not modelled */ + /* 0x7ffd0000: PL354 static memory controller: not modelled */ +} + +static const VEDBoardInfo a15_daughterboard = { + .motherboard_map = motherboard_aseries_map, + .loader_start = 0x80000000, + .gic_cpu_if_addr = 0x2c002000, + .init = a15_daughterboard_init, +}; + static void vexpress_common_init(const VEDBoardInfo *daughterboard, ram_addr_t ram_size, const char *boot_device, @@ -333,6 +453,18 @@ static void vexpress_a9_init(ram_addr_t ram_size, kernel_cmdline, initrd_filename, cpu_model); } +static void vexpress_a15_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model) +{ + vexpress_common_init(&a15_daughterboard, + ram_size, boot_device, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model); +} + static QEMUMachine vexpress_a9_machine = { .name = "vexpress-a9", .desc = "ARM Versatile Express for Cortex-A9", @@ -341,9 +473,18 @@ static QEMUMachine vexpress_a9_machine = { .max_cpus = 4, }; +static QEMUMachine vexpress_a15_machine = { + .name = "vexpress-a15", + .desc = "ARM Versatile Express for Cortex-A15", + .init = vexpress_a15_init, + .use_scsi = 1, + .max_cpus = 4, +}; + static void vexpress_machine_init(void) { qemu_register_machine(&vexpress_a9_machine); + qemu_register_machine(&vexpress_a15_machine); } machine_init(vexpress_machine_init); -- cgit v1.1 From 54de1e5b3a8f02ba5c89606023f6351261068894 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 16 Feb 2012 09:56:09 +0000 Subject: hw/arm_sysctl: Drop legacy init function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the legacy init function arm_sysctl_init(), since it has no users left any more. This allows us to drop the awkward '1' from the actual device init function. Signed-off-by: Peter Maydell Acked-by: Andreas Färber --- hw/arm_sysctl.c | 16 ++-------------- hw/primecell.h | 3 --- 2 files changed, 2 insertions(+), 17 deletions(-) (limited to 'hw') diff --git a/hw/arm_sysctl.c b/hw/arm_sysctl.c index 149c639..5f1237b 100644 --- a/hw/arm_sysctl.c +++ b/hw/arm_sysctl.c @@ -378,7 +378,7 @@ static void arm_sysctl_gpio_set(void *opaque, int line, int level) } } -static int arm_sysctl_init1(SysBusDevice *dev) +static int arm_sysctl_init(SysBusDevice *dev) { arm_sysctl_state *s = FROM_SYSBUS(arm_sysctl_state, dev); @@ -389,18 +389,6 @@ static int arm_sysctl_init1(SysBusDevice *dev) return 0; } -/* Legacy helper function. */ -void arm_sysctl_init(uint32_t base, uint32_t sys_id, uint32_t proc_id) -{ - DeviceState *dev; - - dev = qdev_create(NULL, "realview_sysctl"); - qdev_prop_set_uint32(dev, "sys_id", sys_id); - qdev_init_nofail(dev); - qdev_prop_set_uint32(dev, "proc_id", proc_id); - sysbus_mmio_map(sysbus_from_qdev(dev), 0, base); -} - static Property arm_sysctl_properties[] = { DEFINE_PROP_UINT32("sys_id", arm_sysctl_state, sys_id, 0), DEFINE_PROP_UINT32("proc_id", arm_sysctl_state, proc_id, 0), @@ -412,7 +400,7 @@ static void arm_sysctl_class_init(ObjectClass *klass, void *data) DeviceClass *dc = DEVICE_CLASS(klass); SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); - k->init = arm_sysctl_init1; + k->init = arm_sysctl_init; dc->reset = arm_sysctl_reset; dc->vmsd = &vmstate_arm_sysctl; dc->props = arm_sysctl_properties; diff --git a/hw/primecell.h b/hw/primecell.h index de7d6f2..e709ad3 100644 --- a/hw/primecell.h +++ b/hw/primecell.h @@ -8,9 +8,6 @@ /* pl080.c */ void *pl080_init(uint32_t base, qemu_irq irq, int nchannels); -/* arm_sysctl.c */ -void arm_sysctl_init(uint32_t base, uint32_t sys_id, uint32_t proc_id); - /* arm_sysctl GPIO lines */ #define ARM_SYSCTL_GPIO_MMC_WPROT 0 #define ARM_SYSCTL_GPIO_MMC_CARDIN 1 -- cgit v1.1 From 2a9577034acf77300ce3aa58c3ebffc5949ce96d Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 16 Feb 2012 09:56:10 +0000 Subject: hw/primecell.h: Remove obsolete pl080_init() declaration Remove an obsolete declaration of pl080_init(), which has been incorrect since the conversion of pl080 to qdev back in 2009. Signed-off-by: Peter Maydell --- hw/primecell.h | 3 --- 1 file changed, 3 deletions(-) (limited to 'hw') diff --git a/hw/primecell.h b/hw/primecell.h index e709ad3..7337c3b 100644 --- a/hw/primecell.h +++ b/hw/primecell.h @@ -5,9 +5,6 @@ /* Also includes some devices that are currently only used by the ARM boards. */ -/* pl080.c */ -void *pl080_init(uint32_t base, qemu_irq irq, int nchannels); - /* arm_sysctl GPIO lines */ #define ARM_SYSCTL_GPIO_MMC_WPROT 0 #define ARM_SYSCTL_GPIO_MMC_CARDIN 1 -- cgit v1.1 From 22168e666033c78977d5864e808375ea256b2882 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 16 Feb 2012 09:56:10 +0000 Subject: Remove unnecessary includes of primecell.h The primecell.h header now only has the definitions of constants indicating the usage of the arm_sysctl GPIO lines; remove obsolete includes of it from source files which don't care about those GPIO lines. Signed-off-by: Peter Maydell --- hw/highbank.c | 1 - hw/integratorcp.c | 1 - hw/pl022.c | 1 - hw/versatilepb.c | 1 - 4 files changed, 4 deletions(-) (limited to 'hw') diff --git a/hw/highbank.c b/hw/highbank.c index b28b464..489c00e 100644 --- a/hw/highbank.c +++ b/hw/highbank.c @@ -19,7 +19,6 @@ #include "sysbus.h" #include "arm-misc.h" -#include "primecell.h" #include "devices.h" #include "loader.h" #include "net.h" diff --git a/hw/integratorcp.c b/hw/integratorcp.c index 294d7da..5b06c81 100644 --- a/hw/integratorcp.c +++ b/hw/integratorcp.c @@ -8,7 +8,6 @@ */ #include "sysbus.h" -#include "primecell.h" #include "devices.h" #include "boards.h" #include "arm-misc.h" diff --git a/hw/pl022.c b/hw/pl022.c index 03bf63c..60e35da 100644 --- a/hw/pl022.c +++ b/hw/pl022.c @@ -9,7 +9,6 @@ #include "sysbus.h" #include "ssi.h" -#include "primecell.h" //#define DEBUG_PL022 1 diff --git a/hw/versatilepb.c b/hw/versatilepb.c index 1903db6..b9102f4 100644 --- a/hw/versatilepb.c +++ b/hw/versatilepb.c @@ -9,7 +9,6 @@ #include "sysbus.h" #include "arm-misc.h" -#include "primecell.h" #include "devices.h" #include "net.h" #include "sysemu.h" -- cgit v1.1 From 13a16f1d91fc7a46b65b22a33f6ffea1b826a097 Mon Sep 17 00:00:00 2001 From: Peter Maydell Date: Thu, 16 Feb 2012 09:56:10 +0000 Subject: hw/pl031: Actually raise interrupt on timer expiry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix a typo in pl031_interrupt() which meant we were setting a bit in the interrupt mask rather than the interrupt status register and thus not actually raising an interrupt. This fix allows the rtctest program from the kernel's Documentation/rtc.txt to pass rather than hanging. Reported-by: Daniel Forsgren Signed-off-by: Peter Maydell Acked-by: Andreas Färber --- hw/pl031.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'hw') diff --git a/hw/pl031.c b/hw/pl031.c index 05b5b11..69abc4f 100644 --- a/hw/pl031.c +++ b/hw/pl031.c @@ -76,7 +76,7 @@ static void pl031_interrupt(void * opaque) { pl031_state *s = (pl031_state *)opaque; - s->im = 1; + s->is = 1; DPRINTF("Alarm raised\n"); pl031_update(s); } -- cgit v1.1