/* * 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 "qemu/osdep.h" #include "hw/sysbus.h" #include "migration/vmstate.h" #include "qemu/module.h" #include "hw/arm/exynos4210.h" #include "hw/hw.h" #include "hw/irq.h" #include "hw/qdev-properties.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; #define TYPE_EXYNOS4210_COMBINER "exynos4210.combiner" #define EXYNOS4210_COMBINER(obj) \ OBJECT_CHECK(Exynos4210CombinerState, (obj), TYPE_EXYNOS4210_COMBINER) typedef struct Exynos4210CombinerState { SysBusDevice parent_obj; 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, .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, .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, hwaddr 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; 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 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, hwaddr 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 */ 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; } } /* 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); } 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 void exynos4210_combiner_init(Object *obj) { DeviceState *dev = DEVICE(obj); Exynos4210CombinerState *s = EXYNOS4210_COMBINER(obj); SysBusDevice *sbd = SYS_BUS_DEVICE(obj); unsigned int i; /* Allocate general purpose input signals and connect a handler to each of * them */ qdev_init_gpio_in(dev, exynos4210_combiner_handler, IIC_NIRQ); /* Connect SysBusDev irqs to device specific irqs */ for (i = 0; i < IIC_NGRP; i++) { sysbus_init_irq(sbd, &s->output_irq[i]); } memory_region_init_io(&s->iomem, obj, &exynos4210_combiner_ops, s, "exynos4210-combiner", IIC_REGION_SIZE); sysbus_init_mmio(sbd, &s->iomem); } 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); dc->reset = exynos4210_combiner_reset; device_class_set_props(dc, exynos4210_combiner_properties); dc->vmsd = &vmstate_exynos4210_combiner; } static const TypeInfo exynos4210_combiner_info = { .name = TYPE_EXYNOS4210_COMBINER, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(Exynos4210CombinerState), .instance_init = exynos4210_combiner_init, .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)