Patchwork [v2,18/20] arm: add Faraday FTINTC020 interrupt controller

login
register
mail settings
Submitter Kuo-Jung Su
Date Jan. 25, 2013, 8:19 a.m.
Message ID <1359101996-11667-19-git-send-email-dantesu@gmail.com>
Download mbox | patch
Permalink /patch/215579/
State New
Headers show

Comments

Kuo-Jung Su - Jan. 25, 2013, 8:19 a.m.
From: Kuo-Jung Su <dantesu@faraday-tech.com>

The FTINTC020 interrupt controller supports both FIQ and IRQ signals
to the microprocessor.
It can handle up to 64 configurable IRQ sources and 64 FIQ sources.
The output signals to the microprocessor can be configured as
level-high/low active or edge-rising/falling triggered.

To solve the problem of the interrupt latency, the FTINTC020 supports the
AHB interface and 16 vectored IRQ interrupts. The interrupt priority hardware
engine supports the fix-order, fix-prior, and round-robin schemes.
The priority schemes should be programmable. It also supports the
software-triggered interrupts, which are useful in dealing with the
inter-processor communication. If the vectored IRQs are not enabled,
this controller will be fully compatible to FTINTC010.

Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
---
 hw/ftintc020.c |  354 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 354 insertions(+)
 create mode 100644 hw/ftintc020.c

Patch

diff --git a/hw/ftintc020.c b/hw/ftintc020.c
new file mode 100644
index 0000000..7c5baa2
--- /dev/null
+++ b/hw/ftintc020.c
@@ -0,0 +1,354 @@ 
+/*
+ * Faraday FTINTC020 Programmable Interrupt Controller.
+ *
+ * Copyright (c) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com>
+ *
+ * This code is licensed under the GPL v2.
+ */
+
+#include "hw.h"
+#include "sysbus.h"
+
+#define TYPE_FTINTC020  "ftintc020"
+
+typedef struct Ftintc020State {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    ARMCPU *cpu;
+    qemu_irq irqs[64];
+
+    uint32_t irq_pin[2];    /* IRQ pin state */
+    uint32_t fiq_pin[2];    /* IRQ pin state */
+
+    /* HW register caches */
+    uint32_t irq_src[2];    /* IRQ source register */
+    uint32_t irq_ena[2];    /* IRQ enable register */
+    uint32_t irq_mod[2];    /* IRQ mode register */
+    uint32_t irq_lvl[2];    /* IRQ level register */
+    uint32_t fiq_src[2];    /* FIQ source register */
+    uint32_t fiq_ena[2];    /* FIQ enable register */
+    uint32_t fiq_mod[2];    /* FIQ mode register */
+    uint32_t fiq_lvl[2];    /* FIQ level register */
+} ftintc020_state;
+
+#define FTINTC020(obj) \
+    OBJECT_CHECK(ftintc020_state, obj, TYPE_FTINTC020)
+
+qemu_irq *ftintc020_init(hwaddr base, ARMCPU *cpu);
+
+static void ftintc020_update(ftintc020_state *s)
+{
+    uint32_t mask[2];
+
+    /* FIQ */
+    mask[0] = s->fiq_src[0] & s->fiq_ena[0];
+    mask[1] = s->fiq_src[1] & s->fiq_ena[1];
+
+    if (mask[0] || mask[1]) {
+        cpu_interrupt(&s->cpu->env, CPU_INTERRUPT_FIQ);
+    } else {
+        cpu_reset_interrupt(&s->cpu->env, CPU_INTERRUPT_FIQ);
+    }
+
+    /* IRQ */
+    mask[0] = s->irq_src[0] & s->irq_ena[0];
+    mask[1] = s->irq_src[1] & s->irq_ena[1];
+
+    if (mask[0] || mask[1]) {
+        cpu_interrupt(&s->cpu->env, CPU_INTERRUPT_HARD);
+    } else {
+        cpu_reset_interrupt(&s->cpu->env, CPU_INTERRUPT_HARD);
+    }
+}
+
+/* Note: Here level means state of the signal on a pin */
+static void ftintc020_set_irq(void *opaque, int irq, int level)
+{
+    ftintc020_state *s = FTINTC020(opaque);
+    uint32_t i = irq / 32;
+    uint32_t mask = 1 << (irq % 32);
+
+    if (s->irq_mod[i] & mask) {
+        /* Edge Triggered */
+        if (s->irq_lvl[i] & mask) {
+            /* Falling Active */
+            if ((s->irq_pin[i] & mask) && !level) {
+                s->irq_src[i] |=  mask;
+                s->fiq_src[i] |=  mask;
+            }
+        } else {
+            /* Rising Active */
+            if (!(s->irq_pin[i] & mask) && level) {
+                s->irq_src[i] |=  mask;
+                s->fiq_src[i] |=  mask;
+            }
+        }
+    } else {
+        /* Level Triggered */
+        if (s->irq_lvl[i] & mask) {
+            /* Low Active */
+            if (level) {
+                s->irq_src[i] &= ~mask;
+                s->fiq_src[i] &= ~mask;
+            } else {
+                s->irq_src[i] |=  mask;
+                s->fiq_src[i] |=  mask;
+            }
+        } else {
+            /* High Active */
+            if (!level) {
+                s->irq_src[i] &= ~mask;
+                s->fiq_src[i] &= ~mask;
+            } else {
+                s->irq_src[i] |=  mask;
+                s->fiq_src[i] |=  mask;
+            }
+        }
+    }
+
+    /* update IRQ/FIQ pin states */
+    if (level) {
+        s->irq_pin[i] |= mask;
+        s->fiq_pin[i] |= mask;
+    } else {
+        s->irq_pin[i] &= ~mask;
+        s->fiq_pin[i] &= ~mask;
+    }
+
+    ftintc020_update(s);
+}
+
+static uint64_t ftintc020_mem_read(void *opaque, hwaddr offset, unsigned size)
+{
+    ftintc020_state *s = FTINTC020(opaque);
+
+    switch (offset) {
+    /*
+     * IRQ
+     */
+    case 0x0000:    /* IRQ source register */
+        return s->irq_src[0];
+    case 0x0004:    /* IRQ enable register */
+        return s->irq_ena[0];
+    case 0x000C:    /* IRQ trigger-mode register */
+        return s->irq_mod[0];
+    case 0x0010:    /* IRQ trigger-level register */
+        return s->irq_lvl[0];
+    case 0x0014:    /* IRQ status register */
+        return s->irq_src[0] & s->irq_ena[0];
+    case 0x0060:    /* IRQ source register */
+        return s->irq_src[1];
+    case 0x0064:    /* IRQ enable register */
+        return s->irq_ena[1];
+    case 0x006C:    /* IRQ trigger-mode register */
+        return s->irq_mod[1];
+    case 0x0070:    /* IRQ trigger-level register */
+        return s->irq_lvl[1];
+    case 0x0074:    /* IRQ status register */
+        return s->irq_src[1] & s->irq_ena[1];
+
+    /*
+     * FIQ
+     */
+    case 0x0020:    /* FIQ source register */
+        return s->fiq_src[0];
+    case 0x0024:    /* FIQ enable register */
+        return s->fiq_ena[0];
+    case 0x002C:    /* FIQ trigger-mode register */
+        return s->fiq_mod[0];
+    case 0x0030:    /* FIQ trigger-level register */
+        return s->fiq_lvl[0];
+    case 0x0034:    /* FIQ status register */
+        return s->fiq_src[0] & s->fiq_ena[0];
+    case 0x0080:    /* FIQ source register */
+        return s->fiq_src[1];
+    case 0x0084:    /* FIQ enable register */
+        return s->fiq_ena[1];
+    case 0x008C:    /* FIQ trigger-mode register */
+        return s->fiq_mod[1];
+    case 0x0090:    /* FIQ trigger-level register */
+        return s->fiq_lvl[1];
+    case 0x0094:    /* FIQ status register */
+        return s->fiq_src[1] & s->fiq_ena[1];
+    default:
+        return 0;
+    }
+}
+
+static void ftintc020_mem_write(void    *opaque,
+                                hwaddr   offset,
+                                uint64_t value,
+                                unsigned size)
+{
+    ftintc020_state *s = FTINTC020(opaque);
+
+    switch (offset) {
+    /*
+     * IRQ
+     */
+    case 0x0004:    /* IRQ enable register */
+        s->irq_ena[0] = (uint32_t)value;
+        break;
+    case 0x0008:    /* IRQ interrupt clear register */
+        value = ~(value & s->irq_mod[0]);
+        s->irq_src[0] &= (uint32_t)value;
+        break;
+    case 0x000C:
+        s->irq_mod[0] = (uint32_t)value;
+        break;
+    case 0x0010:
+        s->irq_lvl[0] = (uint32_t)value;
+        break;
+    case 0x0064:    /* IRQ enable register */
+        s->irq_ena[1] = (uint32_t)value;
+        break;
+    case 0x0068:    /* IRQ interrupt clear register */
+        value = ~(value & s->irq_mod[1]);
+        s->irq_src[1] &= (uint32_t)value;
+        break;
+    case 0x006C:
+        s->irq_mod[1] = (uint32_t)value;
+        break;
+    case 0x0070:
+        s->irq_lvl[1] = (uint32_t)value;
+        break;
+
+    /*
+     * FIQ
+     */
+    case 0x0024:    /* FIQ enable register */
+        s->fiq_ena[0] = (uint32_t)value;
+        break;
+    case 0x0028:    /* FIQ interrupt clear register */
+        value = ~(value & s->fiq_mod[0]);
+        s->fiq_src[0] &= (uint32_t)value;
+        break;
+    case 0x002C:
+        s->fiq_mod[0] = (uint32_t)value;
+        break;
+    case 0x0030:
+        s->fiq_lvl[0] = (uint32_t)value;
+        break;
+    case 0x0084:    /* FIQ enable register */
+        s->fiq_ena[1] = (uint32_t)value;
+        break;
+    case 0x0088:    /* FIQ interrupt clear register */
+        value = ~(value & s->fiq_mod[1]);
+        s->fiq_src[1] &= (uint32_t)value;
+        break;
+    case 0x008C:
+        s->fiq_mod[1] = (uint32_t)value;
+        break;
+    case 0x0090:
+        s->fiq_lvl[1] = (uint32_t)value;
+        break;
+    default:
+        return;
+    }
+
+    ftintc020_update(s);
+}
+
+static const MemoryRegionOps ftintc020_mem_ops = {
+    .read  = ftintc020_mem_read,
+    .write = ftintc020_mem_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void ftintc020_reset(DeviceState *ds)
+{
+    SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
+    ftintc020_state *s = FTINTC020(FROM_SYSBUS(ftintc020_state, busdev));
+
+    s->irq_pin[0] = 0;
+    s->irq_pin[1] = 0;
+    s->fiq_pin[0] = 0;
+    s->fiq_pin[1] = 0;
+
+    s->irq_src[0] = 0;
+    s->irq_src[1] = 0;
+    s->irq_ena[0] = 0;
+    s->irq_ena[1] = 0;
+    s->fiq_src[0] = 0;
+    s->fiq_src[1] = 0;
+    s->fiq_ena[0] = 0;
+    s->fiq_ena[1] = 0;
+
+    ftintc020_update(s);
+}
+
+qemu_irq *ftintc020_init(hwaddr base, ARMCPU *cpu)
+{
+    int i;
+    DeviceState *ds = qdev_create(NULL, TYPE_FTINTC020);
+    SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
+    ftintc020_state *s = FTINTC020(FROM_SYSBUS(ftintc020_state, busdev));
+
+    s->cpu = cpu;
+    ftintc020_reset(ds);
+    qdev_init_nofail(ds);
+    qdev_init_gpio_in(ds, ftintc020_set_irq, 64);
+    for (i = 0; i < 64; ++i) {
+        s->irqs[i] = qdev_get_gpio_in(ds, i);
+    }
+
+    /* Enable IC memory-mapped registers access.  */
+    memory_region_init_io(&s->iomem,
+                          &ftintc020_mem_ops,
+                          s,
+                          TYPE_FTINTC020,
+                          0x1000);
+    sysbus_init_mmio(SYS_BUS_DEVICE(ds), &s->iomem);
+    sysbus_mmio_map(SYS_BUS_DEVICE(ds), 0, base);
+
+    return s->irqs;
+}
+
+static VMStateDescription vmstate_ftintc020 = {
+    .name = TYPE_FTINTC020,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(irq_src, ftintc020_state, 2),
+        VMSTATE_UINT32_ARRAY(irq_ena, ftintc020_state, 2),
+        VMSTATE_UINT32_ARRAY(irq_mod, ftintc020_state, 2),
+        VMSTATE_UINT32_ARRAY(irq_lvl, ftintc020_state, 2),
+        VMSTATE_UINT32_ARRAY(fiq_src, ftintc020_state, 2),
+        VMSTATE_UINT32_ARRAY(fiq_ena, ftintc020_state, 2),
+        VMSTATE_UINT32_ARRAY(fiq_mod, ftintc020_state, 2),
+        VMSTATE_UINT32_ARRAY(fiq_lvl, ftintc020_state, 2),
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static int ftintc020_initfn(SysBusDevice *dev)
+{
+    return 0;
+}
+
+static void ftintc020_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    k->init     = ftintc020_initfn;
+    dc->vmsd    = &vmstate_ftintc020;
+    dc->reset   = ftintc020_reset;
+    dc->no_user = 1;
+}
+
+static const TypeInfo ftintc020_info = {
+    .name          = TYPE_FTINTC020,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(ftintc020_state),
+    .class_init    = ftintc020_class_init,
+};
+
+static void ftintc020_register_types(void)
+{
+    type_register_static(&ftintc020_info);
+}
+
+type_init(ftintc020_register_types)