@@ -37,3 +37,4 @@ obj-y += omap1.o omap2.o
obj-y += faraday_a369.o faraday_a369_soc.o faraday_a369_scu.o \
faraday_a369_kpd.o
+obj-y += ftintc020.o
@@ -72,6 +72,8 @@ a369soc_device_init(FaradaySoCState *s)
{
DriveInfo *dinfo;
DeviceState *ds;
+ qemu_irq *cpu_pic;
+ int i;
s->as = get_system_memory();
s->ram = g_new(MemoryRegion, 1);
@@ -111,6 +113,15 @@ a369soc_device_init(FaradaySoCState *s)
abort();
}
+ /* Interrupt Controller */
+ cpu_pic = arm_pic_init_cpu(s->cpu);
+ ds = sysbus_create_varargs("ftintc020", 0x90100000,
+ cpu_pic[ARM_PIC_CPU_IRQ],
+ cpu_pic[ARM_PIC_CPU_FIQ], NULL);
+ for (i = 0; i < ARRAY_SIZE(s->pic); ++i) {
+ s->pic[i] = qdev_get_gpio_in(ds, i);
+ }
+
/* Serial (FTUART010 which is 16550A compatible) */
if (serial_hds[0]) {
serial_mm_init(s->as,
new file mode 100644
@@ -0,0 +1,302 @@
+/*
+ * Faraday FTINTC020 Programmable Interrupt Controller.
+ *
+ * Copyright (c) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com>
+ *
+ * This code is licensed under GNU GPL v2+.
+ */
+
+#include "hw/hw.h"
+#include "hw/sysbus.h"
+
+#include "ftintc020.h"
+
+#define TYPE_FTINTC020 "ftintc020"
+
+#define CFG_REGSIZE (0x100 / 4)
+
+typedef struct Ftintc020State {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+
+ qemu_irq irq;
+ qemu_irq fiq;
+
+ uint32_t irq_ps[2]; /* IRQ pin state */
+ uint32_t fiq_ps[2]; /* FIQ pin state */
+
+ /* HW register caches */
+ uint32_t regs[CFG_REGSIZE];
+} Ftintc020State;
+
+#define FTINTC020(obj) \
+ OBJECT_CHECK(Ftintc020State, obj, TYPE_FTINTC020)
+
+#define PIC_REG32(s, off) \
+ ((s)->regs[(off) / 4])
+
+#define IRQ_REG32(s, n, off) \
+ ((s)->regs[(REG_IRQ(n) + ((off) & REG_MASK)) / 4])
+
+#define FIQ_REG32(s, n, off) \
+ ((s)->regs[(REG_FIQ(n) + ((off) & REG_MASK)) / 4])
+
+static void
+ftintc020_update_irq(Ftintc020State *s)
+{
+ uint32_t mask[2];
+
+ /* FIQ */
+ mask[0] = PIC_REG32(s, REG_FIQ32SRC) & PIC_REG32(s, REG_FIQ32ENA);
+ mask[1] = PIC_REG32(s, REG_FIQ64SRC) & PIC_REG32(s, REG_FIQ64ENA);
+ qemu_set_irq(s->fiq, !!(mask[0] || mask[1]));
+
+ /* IRQ */
+ mask[0] = PIC_REG32(s, REG_IRQ32SRC) & PIC_REG32(s, REG_IRQ32ENA);
+ mask[1] = PIC_REG32(s, REG_IRQ64SRC) & PIC_REG32(s, REG_IRQ64ENA);
+ qemu_set_irq(s->irq, !!(mask[0] || mask[1]));
+}
+
+/* Note: Here level means state of the signal on a pin */
+static void
+ftintc020_set_irq(void *opaque, int irq, int level)
+{
+ Ftintc020State *s = FTINTC020(opaque);
+ uint32_t i = irq / 32;
+ uint32_t mask = BIT(irq & 0x1f);
+
+ switch (irq) {
+ case 0 ... 63:
+ /* IRQ */
+ if (IRQ_REG32(s, irq, REG_MDR) & mask) {
+ /* Edge Triggered */
+ if (IRQ_REG32(s, irq, REG_LVR) & mask) {
+ /* Falling Active */
+ if ((s->irq_ps[i] & mask) && !level) {
+ IRQ_REG32(s, irq, REG_SRC) |= mask;
+ }
+ } else {
+ /* Rising Active */
+ if (!(s->irq_ps[i] & mask) && level) {
+ IRQ_REG32(s, irq, REG_SRC) |= mask;
+ }
+ }
+ } else {
+ /* Level Triggered */
+ if (IRQ_REG32(s, irq, REG_LVR) & mask) {
+ /* Low Active */
+ if (level) {
+ IRQ_REG32(s, irq, REG_SRC) &= ~mask;
+ } else {
+ IRQ_REG32(s, irq, REG_SRC) |= mask;
+ }
+ } else {
+ /* High Active */
+ if (level) {
+ IRQ_REG32(s, irq, REG_SRC) |= mask;
+ } else {
+ IRQ_REG32(s, irq, REG_SRC) &= ~mask;
+ }
+ }
+ }
+
+ /* FIQ */
+ if (FIQ_REG32(s, irq, REG_MDR) & mask) {
+ /* Edge Triggered */
+ if (FIQ_REG32(s, irq, REG_LVR) & mask) {
+ /* Falling Active */
+ if ((s->fiq_ps[i] & mask) && !level) {
+ FIQ_REG32(s, irq, REG_SRC) |= mask;
+ }
+ } else {
+ /* Rising Active */
+ if (!(s->fiq_ps[i] & mask) && level) {
+ FIQ_REG32(s, irq, REG_SRC) |= mask;
+ }
+ }
+ } else {
+ /* Level Triggered */
+ if (FIQ_REG32(s, irq, REG_LVR) & mask) {
+ /* Low Active */
+ if (level) {
+ FIQ_REG32(s, irq, REG_SRC) &= ~mask;
+ } else {
+ FIQ_REG32(s, irq, REG_SRC) |= mask;
+ }
+ } else {
+ /* High Active */
+ if (level) {
+ FIQ_REG32(s, irq, REG_SRC) |= mask;
+ } else {
+ FIQ_REG32(s, irq, REG_SRC) &= ~mask;
+ }
+ }
+ }
+ /* update IRQ/FIQ pin states */
+ if (level) {
+ s->irq_ps[i] |= mask;
+ s->fiq_ps[i] |= mask;
+ } else {
+ s->irq_ps[i] &= ~mask;
+ s->fiq_ps[i] &= ~mask;
+ }
+ break;
+
+ default:
+ fprintf(stderr, "ftintc020: undefined irq=%d\n", irq);
+ abort();
+ }
+
+ ftintc020_update_irq(s);
+}
+
+static uint64_t
+ftintc020_mem_read(void *opaque, hwaddr addr, unsigned size)
+{
+ Ftintc020State *s = FTINTC020(opaque);
+ uint32_t ret = 0;
+
+ if (addr > REG_FIQ64SR) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ftintc020: undefined memory access@%#" HWADDR_PRIx "\n", addr);
+ return ret;
+ }
+
+ switch (addr) {
+ case REG_IRQ32SR:
+ ret = PIC_REG32(s, REG_IRQ32SRC) & PIC_REG32(s, REG_IRQ32ENA);
+ break;
+ case REG_IRQ64SR:
+ ret = PIC_REG32(s, REG_IRQ64SRC) & PIC_REG32(s, REG_IRQ64ENA);
+ break;
+ case REG_FIQ32SR:
+ ret = PIC_REG32(s, REG_FIQ32SRC) & PIC_REG32(s, REG_FIQ32ENA);
+ break;
+ case REG_FIQ64SR:
+ ret = PIC_REG32(s, REG_FIQ64SRC) & PIC_REG32(s, REG_FIQ64ENA);
+ break;
+ default:
+ ret = s->regs[addr / 4];
+ break;
+ }
+
+ return ret;
+}
+
+static void
+ftintc020_mem_write(void *opaque, hwaddr addr, uint64_t value, unsigned size)
+{
+ Ftintc020State *s = FTINTC020(opaque);
+
+ if (addr > REG_FIQ64SR) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ftintc020: undefined memory access@%#" HWADDR_PRIx "\n", addr);
+ return;
+ }
+
+ switch (addr) {
+ case REG_IRQ32SCR:
+ /* clear edge triggered interrupts only */
+ value = ~(value & PIC_REG32(s, REG_IRQ32MDR));
+ PIC_REG32(s, REG_IRQ32SRC) &= (uint32_t)value;
+ break;
+ case REG_IRQ64SCR:
+ /* clear edge triggered interrupts only */
+ value = ~(value & PIC_REG32(s, REG_IRQ64MDR));
+ PIC_REG32(s, REG_IRQ64SRC) &= (uint32_t)value;
+ break;
+ case REG_FIQ32SCR:
+ /* clear edge triggered interrupts only */
+ value = ~(value & PIC_REG32(s, REG_FIQ32MDR));
+ PIC_REG32(s, REG_FIQ32SRC) &= (uint32_t)value;
+ break;
+ case REG_FIQ64SCR:
+ /* clear edge triggered interrupts only */
+ value = ~(value & PIC_REG32(s, REG_FIQ64MDR));
+ PIC_REG32(s, REG_FIQ64SRC) &= (uint32_t)value;
+ break;
+ default:
+ s->regs[addr / 4] = (uint32_t)value;
+ break;
+ }
+
+ ftintc020_update_irq(s);
+}
+
+static const MemoryRegionOps mmio_ops = {
+ .read = ftintc020_mem_read,
+ .write = ftintc020_mem_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ }
+};
+
+static void ftintc020_reset(DeviceState *ds)
+{
+ Ftintc020State *s = FTINTC020(SYS_BUS_DEVICE(ds));
+
+ s->irq_ps[0] = 0;
+ s->irq_ps[1] = 0;
+ s->fiq_ps[0] = 0;
+ s->fiq_ps[1] = 0;
+ memset(s->regs, 0, sizeof(s->regs));
+
+ ftintc020_update_irq(s);
+}
+
+static VMStateDescription vmstate_ftintc020 = {
+ .name = TYPE_FTINTC020,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, Ftintc020State, CFG_REGSIZE),
+ VMSTATE_UINT32_ARRAY(irq_ps, Ftintc020State, 2),
+ VMSTATE_UINT32_ARRAY(fiq_ps, Ftintc020State, 2),
+ VMSTATE_END_OF_LIST(),
+ },
+};
+
+static void ftintc020_realize(DeviceState *dev, Error **errp)
+{
+ Ftintc020State *s = FTINTC020(dev);
+
+ /* Enable IC memory-mapped registers access. */
+ memory_region_init_io(&s->iomem,
+ &mmio_ops,
+ s,
+ TYPE_FTINTC020,
+ 0x1000);
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+
+ qdev_init_gpio_in(&s->busdev.qdev, ftintc020_set_irq, 64);
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->fiq);
+}
+
+static void ftintc020_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_ftintc020;
+ dc->reset = ftintc020_reset;
+ dc->realize = ftintc020_realize;
+ dc->no_user = 1;
+}
+
+static const TypeInfo ftintc020_info = {
+ .name = TYPE_FTINTC020,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Ftintc020State),
+ .class_init = ftintc020_class_init,
+};
+
+static void ftintc020_register_types(void)
+{
+ type_register_static(&ftintc020_info);
+}
+
+type_init(ftintc020_register_types)
new file mode 100644
@@ -0,0 +1,57 @@
+/*
+ * Faraday FTINTC020 Programmable Interrupt Controller.
+ *
+ * Copyright (c) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com>
+ *
+ * This code is licensed under GNU GPL v2+.
+ */
+
+#ifndef HW_ARM_FTINTC020_H
+#define HW_ARM_FTINTC020_H
+
+#include "qemu/bitops.h"
+
+/* IRQ/FIO: 0 ~ 31 */
+#define REG_IRQ32SRC 0x00 /* IRQ source register */
+#define REG_IRQ32ENA 0x04 /* IRQ enable register */
+#define REG_IRQ32SCR 0x08 /* IRQ status clear register */
+#define REG_IRQ32MDR 0x0C /* IRQ trigger mode register */
+#define REG_IRQ32LVR 0x10 /* IRQ trigger level register */
+#define REG_IRQ32SR 0x14 /* IRQ status register */
+
+#define REG_FIQ32SRC 0x20 /* FIQ source register */
+#define REG_FIQ32ENA 0x24 /* FIQ enable register */
+#define REG_FIQ32SCR 0x28 /* FIQ status clear register */
+#define REG_FIQ32MDR 0x2C /* FIQ trigger mode register */
+#define REG_FIQ32LVR 0x30 /* FIQ trigger level register */
+#define REG_FIQ32SR 0x34 /* FIQ status register */
+
+/* Extended IRQ/FIO: 32 ~ 63 */
+#define REG_IRQ64SRC 0x60 /* Extended IRQ source register */
+#define REG_IRQ64ENA 0x64 /* Extended IRQ enable register */
+#define REG_IRQ64SCR 0x68 /* Extended IRQ status clear register */
+#define REG_IRQ64MDR 0x6C /* Extended IRQ trigger mode register */
+#define REG_IRQ64LVR 0x70 /* Extended IRQ trigger level register */
+#define REG_IRQ64SR 0x74 /* Extended IRQ status register */
+
+#define REG_FIQ64SRC 0x80 /* Extended FIQ source register */
+#define REG_FIQ64ENA 0x84 /* Extended FIQ enable register */
+#define REG_FIQ64SCR 0x88 /* Extended FIQ status clear register */
+#define REG_FIQ64MDR 0x8C /* Extended FIQ trigger mode register */
+#define REG_FIQ64LVR 0x90 /* Extended FIQ trigger level register */
+#define REG_FIQ64SR 0x94 /* Extended FIQ status register */
+
+/* Common register offset to IRQ/FIQ */
+#define REG_SRC 0x00 /* Source register */
+#define REG_ENA 0x04 /* Enable register */
+#define REG_SCR 0x08 /* Status clear register */
+#define REG_MDR 0x0C /* Trigger mode register */
+#define REG_LVR 0x10 /* Trigger level register */
+#define REG_SR 0x14 /* Status register */
+#define REG_MASK 0x1f
+
+#define REG_IRQ(n) (((n) < 32) ? REG_IRQ32SRC : REG_IRQ64SRC)
+#define REG_FIQ(n) (((n) < 32) ? REG_FIQ32SRC : REG_FIQ64SRC)
+
+#endif