From patchwork Mon Dec 12 06:43:17 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Evgeny Voevodin X-Patchwork-Id: 130652 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [140.186.70.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 5FD10B6F70 for ; Mon, 12 Dec 2011 18:27:48 +1100 (EST) Received: from localhost ([::1]:56569 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1RZzd8-0007jh-Eg for incoming@patchwork.ozlabs.org; Mon, 12 Dec 2011 01:44:54 -0500 Received: from eggs.gnu.org ([140.186.70.92]:51763) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1RZzc3-0005Ty-UG for qemu-devel@nongnu.org; Mon, 12 Dec 2011 01:43:53 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1RZzbw-0002fX-5I for qemu-devel@nongnu.org; Mon, 12 Dec 2011 01:43:47 -0500 Received: from mailout1.w1.samsung.com ([210.118.77.11]:40920) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1RZzbv-0002eQ-Pe for qemu-devel@nongnu.org; Mon, 12 Dec 2011 01:43:40 -0500 Received: from euspt2 (mailout1.w1.samsung.com [210.118.77.11]) by mailout1.w1.samsung.com (iPlanet Messaging Server 5.2 Patch 2 (built Jul 14 2004)) with ESMTP id <0LW200EJ0W0ORW@mailout1.w1.samsung.com> for qemu-devel@nongnu.org; Mon, 12 Dec 2011 06:43:36 +0000 (GMT) Received: from evvoevodinPC.rnd.samsung.ru ([106.109.8.48]) by spt2.w1.samsung.com (iPlanet Messaging Server 5.2 Patch 2 (built Jul 14 2004)) with ESMTPA id <0LW200LRSW0GC5@spt2.w1.samsung.com> for qemu-devel@nongnu.org; Mon, 12 Dec 2011 06:43:36 +0000 (GMT) Date: Mon, 12 Dec 2011 10:43:17 +0400 From: Evgeny Voevodin In-reply-to: <1323672206-11891-1-git-send-email-e.voevodin@samsung.com> To: qemu-devel@nongnu.org Message-id: <1323672206-11891-6-git-send-email-e.voevodin@samsung.com> MIME-version: 1.0 X-Mailer: git-send-email 1.7.4.1 Content-type: TEXT/PLAIN Content-transfer-encoding: 7BIT References: <1323672206-11891-1-git-send-email-e.voevodin@samsung.com> X-detected-operating-system: by eggs.gnu.org: Solaris 9.1 X-Received-From: 210.118.77.11 Cc: m.kozlov@samsung.com, d.solodkiy@samsung.com, Evgeny Voevodin Subject: [Qemu-devel] [PATCH v3 05/14] ARM: exynos4210: IRQ subsystem support. X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Signed-off-by: Evgeny Voevodin --- Makefile.target | 3 +- hw/exynos4210.c | 175 ++++++++++++++++- hw/exynos4210.h | 42 ++++ hw/exynos4210_combiner.c | 385 ++++++++++++++++++++++++++++++++++ hw/exynos4210_gic.c | 510 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1113 insertions(+), 2 deletions(-) create mode 100644 hw/exynos4210_combiner.c create mode 100644 hw/exynos4210_gic.c diff --git a/Makefile.target b/Makefile.target index 4c706b1..779c9d4 100644 --- a/Makefile.target +++ b/Makefile.target @@ -344,7 +344,8 @@ obj-arm-y = integratorcp.o versatilepb.o arm_pic.o arm_timer.o obj-arm-y += arm_boot.o pl011.o pl031.o pl050.o pl080.o pl110.o pl181.o pl190.o obj-arm-y += versatile_pci.o obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o -obj-arm-y += exynos4210.o exynos4210_cmu.o exynos4210_uart.o +obj-arm-y += exynos4210.o exynos4210_cmu.o exynos4210_uart.o exynos4210_gic.o \ + exynos4210_combiner.o obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o obj-arm-y += pl061.o obj-arm-y += arm-semi.o diff --git a/hw/exynos4210.c b/hw/exynos4210.c index d5a1fe0..45d427e 100644 --- a/hw/exynos4210.c +++ b/hw/exynos4210.c @@ -58,6 +58,8 @@ #define EXYNOS4210_SFR_BASE_ADDR 0x10000000 +#define EXYNOS4210_SMP_PRIVATE_BASE_ADDR 0x10500000 + #define EXYNOS4210_BASE_BOOT_ADDR EXYNOS4210_DRAM0_BASE_ADDR /* SFR Base Address for CMUs */ @@ -74,7 +76,16 @@ #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_INTG 26 + +/* 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 struct arm_boot_info exynos4210_binfo = { .loader_start = EXYNOS4210_BASE_BOOT_ADDR, @@ -93,6 +104,87 @@ enum exynos4210_mach_id { MACH_SMDKC210_ID = 0xB16, }; +static 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; + break; + + /* 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; + break; + + /* 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; + break; + + /* 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; + break; + + /* 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; + break; + + /* 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; + break; + } + + irq[n] = qdev_get_gpio_in(dev, n); + } +} static void exynos4210_init(ram_addr_t ram_size, const char *boot_device, @@ -110,8 +202,13 @@ static void exynos4210_init(ram_addr_t ram_size, MemoryRegion *irom_alias_mem = g_new(MemoryRegion, 1); MemoryRegion *dram0_mem = g_new(MemoryRegion, 1); MemoryRegion *dram1_mem = NULL; + Exynos4210Irq *irqs; + qemu_irq *irq_table; qemu_irq *irqp; qemu_irq cpu_irq[4]; + qemu_irq gate_irq[EXYNOS4210_IRQ_GATE_NINPUTS]; + DeviceState *dev; + SysBusDevice *busdev; ram_addr_t mem_size; int n; @@ -129,6 +226,12 @@ static void exynos4210_init(ram_addr_t ram_size, cpu_model = "cortex-a9"; } + irqs = g_malloc0(sizeof(Exynos4210Irq)); + if (!irqs) { + fprintf(stderr, "Can't allocate IRQs\n"); + exit(1); + } + for (n = 0; n < smp_cpus; n++) { env = cpu_init(cpu_model); if (!env) { @@ -145,6 +248,76 @@ static void exynos4210_init(ram_addr_t ram_size, cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ]; } + /*** IRQs ***/ + + irq_table = exynos4210_init_irq(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 < smp_cpus; 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", smp_cpus); + qdev_init_nofail(dev); + busdev = sysbus_from_qdev(dev); + sysbus_mmio_map(busdev, 0, EXYNOS4210_SMP_PRIVATE_BASE_ADDR); + for (n = 0; n < smp_cpus; n++) { + sysbus_connect_irq(busdev, n, gate_irq[n * 2]); + } + for (n = 0; n < EXYNOS4210_INT_GIC_NIRQ; n++) { + irqs->int_gic_irq[n] = qdev_get_gpio_in(dev, n); + } + + /* External GIC */ + dev = qdev_create(NULL, "exynos4210.gic"); + qdev_prop_set_uint32(dev, "num-cpu", smp_cpus); + 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 < smp_cpus; n++) { + sysbus_connect_irq(busdev, n, gate_irq[n * 2 + 1]); + } + for (n = 0; n < EXYNOS4210_EXT_GIC_NIRQ; n++) { + 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, irqs->int_gic_irq[n]); + } + exynos4210_combiner_get_gpioin(irqs, dev, 0); + sysbus_mmio_map(busdev, 0, EXYNOS4210_INT_COMBINER_BASE_ADDR); + + /* External 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, irqs->ext_gic_irq[n]); + } + exynos4210_combiner_get_gpioin(irqs, dev, 1); + sysbus_mmio_map(busdev, 0, EXYNOS4210_EXT_COMBINER_BASE_ADDR); + qdev_prop_set_uint32(dev, "external", 1); + + /* Initialize board IRQs. */ + exynos4210_init_board_irqs(irqs); + /*** Memory ***/ /* Chip-ID and OMR */ @@ -224,7 +397,7 @@ static void exynos4210_init(ram_addr_t ram_size, continue; } - uart_irq = NULL; + uart_irq = irq_table[exynos4210_get_irq(EXYNOS4210_UART_INTG, channel)]; exynos4210_uart_create(addr, fifo_size, channel, NULL, uart_irq); } diff --git a/hw/exynos4210.h b/hw/exynos4210.h index 3df7322..f0dfcaf 100644 --- a/hw/exynos4210.h +++ b/hw/exynos4210.h @@ -32,6 +32,48 @@ #define EXYNOS4210_MAX_CPUS 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); + +/* * Interface for exynos4210 Clock Management Units (CMUs) */ diff --git a/hw/exynos4210_combiner.c b/hw/exynos4210_combiner.c new file mode 100644 index 0000000..6a080d0 --- /dev/null +++ b/hw/exynos4210_combiner.c @@ -0,0 +1,385 @@ +/* + * 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, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#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 + +/* + * Combiner registers + */ +struct CombinerReg { + uint32_t iiesr; /* Internal Interrupt Enable Set register */ + uint32_t iiecr; /* Internal Interrupt Enable Clear register */ + uint32_t iistr; /* Internal Interrupt Status register. + * Shows status of interrupt pending BEFORE masking + */ + uint32_t iimsr; /* Internal Interrupt Mask Status register. + * Shows status of interrupt pending AFTER masking + */ +}; + +/* + * 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_Exynos4210CombinerGroupState = { + .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_Exynos4210Combiner = { + .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_Exynos4210CombinerGroupState, CombinerGroupState), + VMSTATE_UINT32_ARRAY(reg_set, Exynos4210CombinerState, + IIC_REGSET_SIZE), + VMSTATE_UINT32_ARRAY(icipsr, Exynos4210CombinerState, 2), + VMSTATE_UINT32(external, Exynos4210CombinerState), + VMSTATE_END_OF_LIST() + } +}; + +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); + + exynos4210_combiner_reset((DeviceState *)s); + return 0; +} + +static SysBusDeviceInfo exynos4210_combiner_info = { + .qdev.name = "exynos4210.combiner", + .qdev.size = sizeof(struct Exynos4210CombinerState), + .qdev.reset = exynos4210_combiner_reset, + .qdev.vmsd = &VMState_Exynos4210Combiner, + .init = exynos4210_combiner_init, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("external", + struct Exynos4210CombinerState, + external, + 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void exynos4210_combiner_register_devices(void) +{ + sysbus_register_withprop(&exynos4210_combiner_info); +} + +device_init(exynos4210_combiner_register_devices) diff --git a/hw/exynos4210_gic.c b/hw/exynos4210_gic.c new file mode 100644 index 0000000..f43e57f --- /dev/null +++ b/hw/exynos4210_gic.c @@ -0,0 +1,510 @@ +/* + * 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, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "sysbus.h" +#include "qemu-common.h" +#include "irq.h" +#include "exynos4210.h" + +//#define DEBUG_EXYNOS4210_IRQ +//#define DEBUG_EXYNOS4210_GIC + +#ifdef DEBUG_EXYNOS4210_IRQ +#define DPRINTF_EXYNOS4210_IRQ(fmt, ...) \ + do { fprintf(stdout, "IRQ_GATE: " fmt, ## __VA_ARGS__); } while (0) +#else +#define DPRINTF_EXYNOS4210_IRQ(fmt, ...) do {} while (0) +#endif + +#ifdef DEBUG_EXYNOS4210_GIC +#define DPRINTF_EXYNOS4210_GIC(fmt, ...) \ + do { fprintf(stdout, "EXT_GIC: " fmt, ## __VA_ARGS__); } while (0) +#else +#define DPRINTF_EXYNOS4210_GIC(fmt, ...) do {} while (0) +#endif + +#define EXT_GIC_ID_TVENC 127 +#define EXT_GIC_ID_MFC 126 +#define EXT_GIC_ID_HDMI_I2C 125 +#define EXT_GIC_ID_HDMI 124 +#define EXT_GIC_ID_MIXER 123 +#define EXT_GIC_ID_PCIe 122 +#define EXT_GIC_ID_2D 121 +#define EXT_GIC_ID_JPEG 120 +#define EXT_GIC_ID_FIMC3 119 +#define EXT_GIC_ID_FIMC2 118 +#define EXT_GIC_ID_FIMC1 117 +#define EXT_GIC_ID_FIMC0 116 +#define EXT_GIC_ID_ROTATOR 115 +#define EXT_GIC_ID_ONENAND_AUDI 114 +#define EXT_GIC_ID_MIPI_DSI_2LANE 113 +#define EXT_GIC_ID_MIPI_CSI_2LANE 112 +#define EXT_GIC_ID_MIPI_DSI_4LANE 111 +#define EXT_GIC_ID_MIPI_CSI_4LANE 110 +#define EXT_GIC_ID_SDMMC 109 +#define EXT_GIC_ID_HSMMC3 108 +#define EXT_GIC_ID_HSMMC2 107 +#define EXT_GIC_ID_HSMMC1 106 +#define EXT_GIC_ID_HSMMC0 105 +#define EXT_GIC_ID_MODEMIF 104 +#define EXT_GIC_ID_USB_DEVICE 103 +#define EXT_GIC_ID_USB_HOST 102 +#define EXT_GIC_ID_MCT_G1 101 +#define EXT_GIC_ID_SPI2 100 +#define EXT_GIC_ID_SPI1 99 +#define EXT_GIC_ID_SPI0 98 +#define EXT_GIC_ID_I2C7 97 +#define EXT_GIC_ID_I2C6 96 +#define EXT_GIC_ID_I2C5 95 +#define EXT_GIC_ID_I2C4 94 +#define EXT_GIC_ID_I2C3 93 +#define EXT_GIC_ID_I2C2 92 +#define EXT_GIC_ID_I2C1 91 +#define EXT_GIC_ID_I2C0 90 +#define EXT_GIC_ID_MCT_G0 89 +#define EXT_GIC_ID_UART4 88 +#define EXT_GIC_ID_UART3 87 +#define EXT_GIC_ID_UART2 86 +#define EXT_GIC_ID_UART1 85 +#define EXT_GIC_ID_UART0 84 +#define EXT_GIC_ID_NFC 83 +#define EXT_GIC_ID_IEM_IEC 82 +#define EXT_GIC_ID_IEM_APC 81 +#define EXT_GIC_ID_MCT_L1 80 +#define EXT_GIC_ID_GPIO_XA 79 +#define EXT_GIC_ID_GPIO_XB 78 +#define EXT_GIC_ID_RTC_TIC 77 +#define EXT_GIC_ID_RTC_ALARM 76 +#define EXT_GIC_ID_WDT 75 +#define EXT_GIC_ID_MCT_L0 74 +#define EXT_GIC_ID_TIMER4 73 +#define EXT_GIC_ID_TIMER3 72 +#define EXT_GIC_ID_TIMER2 71 +#define EXT_GIC_ID_TIMER1 70 +#define EXT_GIC_ID_TIMER0 69 +#define EXT_GIC_ID_PDMA1 68 +#define EXT_GIC_ID_PDMA0 67 +#define EXT_GIC_ID_MDMA_LCD0 66 + +enum ext_int { + 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 GIC_NIRQ 160 +#define NCPU EXYNOS4210_MAX_CPUS + +#define EXYNOS4210_GIC_CPU_REGION_SIZE 0x8050 +#define EXYNOS4210_GIC_DIST_REGION_SIZE 0x8F04 + +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; + uint32_t num_cpu; +} Exynos4210GicState; + +static uint64_t exynos4210_gic_cpu_read(void *opaque, target_phys_addr_t offset, + unsigned size) +{ + Exynos4210GicState *s = (Exynos4210GicState *) opaque; + DPRINTF_EXYNOS4210_GIC("CPU%d: read offset 0x%x\n", + gic_get_current_cpu(), offset); + return gic_cpu_read(&s->gic, gic_get_current_cpu(), offset & ~0x8000); +} + +static void exynos4210_gic_cpu_write(void *opaque, target_phys_addr_t offset, + uint64_t value, unsigned size) +{ + Exynos4210GicState *s = (Exynos4210GicState *) opaque; + DPRINTF_EXYNOS4210_GIC("CPU%d: write offset 0x%x, value 0x%llx\n", + gic_get_current_cpu(), offset, value); + gic_cpu_write(&s->gic, gic_get_current_cpu(), offset & ~0x8000, value); +} + +static uint32_t +exynos4210_gic_dist_readb(void *opaque, target_phys_addr_t offset) +{ + Exynos4210GicState *s = (Exynos4210GicState *) opaque; + DPRINTF_EXYNOS4210_GIC("DIST: readb offset 0x%x\n", offset); + return gic_dist_readb(&s->gic, offset & ~0x8000); +} + +static uint32_t +exynos4210_gic_dist_readw(void *opaque, target_phys_addr_t offset) +{ + Exynos4210GicState *s = (Exynos4210GicState *) opaque; + DPRINTF_EXYNOS4210_GIC("DIST: readw offset 0x%x\n", offset); + return gic_dist_readw(&s->gic, offset & ~0x8000); +} + +static uint32_t +exynos4210_gic_dist_readl(void *opaque, target_phys_addr_t offset) +{ + Exynos4210GicState *s = (Exynos4210GicState *) opaque; + DPRINTF_EXYNOS4210_GIC("DIST: readl offset 0x%x\n", offset); + return gic_dist_readl(&s->gic, offset & ~0x8000); +} + +static void +exynos4210_gic_dist_writeb(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + Exynos4210GicState *s = (Exynos4210GicState *) opaque; + DPRINTF_EXYNOS4210_GIC("DIST: writeb offset 0x%x, value 0x%x\n", offset, + value); + gic_dist_writeb(&s->gic, offset & ~0x8000, value); +} + +static void exynos4210_gic_dist_writew(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + Exynos4210GicState *s = (Exynos4210GicState *) opaque; + DPRINTF_EXYNOS4210_GIC("DIST: writew offset 0x%x, value 0x%x\n", offset, + value); + gic_dist_writew(&s->gic, offset & ~0x8000, value); +} + +static void exynos4210_gic_dist_writel(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + Exynos4210GicState *s = (Exynos4210GicState *) opaque; + DPRINTF_EXYNOS4210_GIC("DIST: writel offset 0x%x, value 0x%x\n", offset, + value); + gic_dist_writel(&s->gic, offset & ~0x8000, value); +} + +static const MemoryRegionOps exynos4210_gic_cpu_ops = { + .read = exynos4210_gic_cpu_read, + .write = exynos4210_gic_cpu_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const MemoryRegionOps exynos4210_gic_dist_ops = { + .old_mmio = { + .read = { exynos4210_gic_dist_readb, + exynos4210_gic_dist_readw, + exynos4210_gic_dist_readl, }, + .write = { exynos4210_gic_dist_writeb, + exynos4210_gic_dist_writew, + exynos4210_gic_dist_writel, }, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int exynos4210_gic_init(SysBusDevice *dev) +{ + Exynos4210GicState *s = FROM_SYSBUSGIC(Exynos4210GicState, dev); + gic_init(&s->gic, s->num_cpu); + + memory_region_init(&s->cpu_container, "exynos4210-gic-cpu_container", + EXYNOS4210_GIC_CPU_REGION_SIZE); + memory_region_init(&s->cpu_container, "exynos4210-gic-dist_container", + EXYNOS4210_GIC_DIST_REGION_SIZE); + memory_region_init_io(&s->cpu_container, &exynos4210_gic_cpu_ops, &s->gic, + "exynos4210-gic-cpu", EXYNOS4210_GIC_CPU_REGION_SIZE); + memory_region_init_io(&s->dist_container, &exynos4210_gic_dist_ops, &s->gic, + "exynos4210-gic-dist", EXYNOS4210_GIC_DIST_REGION_SIZE); + + 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 SysBusDeviceInfo exynos4210_gic_info = { + .init = exynos4210_gic_init, + .qdev.name = "exynos4210.gic", + .qdev.size = sizeof(Exynos4210GicState), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("num-cpu", Exynos4210GicState, num_cpu, 1), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void exynos4210_gic_register_devices(void) +{ + sysbus_register_withprop(&exynos4210_gic_info); +} + +device_init(exynos4210_gic_register_devices) + +/* + * 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_Exynos4210IRQGate = { + .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; + + DPRINTF_EXYNOS4210_IRQ("odd level=0x%x, even level=0x%x\n", + s->gpio_level[odd], s->gpio_level[even]); + + 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 SysBusDeviceInfo exynos4210_irq_gate_info = { + .qdev.name = "exynos4210.irq_gate", + .qdev.size = sizeof(Exynos4210IRQGateState), + .qdev.reset = exynos4210_irq_gate_reset, + .qdev.vmsd = &VMState_Exynos4210IRQGate, + .init = exynos4210_irq_gate_init, +}; + +static void exynos4210_irq_gate_register_devices(void) +{ + sysbus_register_withprop(&exynos4210_irq_gate_info); +} + +device_init(exynos4210_irq_gate_register_devices)