Patchwork [v9,03/24] hw/arm: add FTINTC020 interrupt controller support

login
register
mail settings
Submitter Kuo-Jung Su
Date March 25, 2013, 12:09 p.m.
Message ID <1364213400-10266-4-git-send-email-dantesu@gmail.com>
Download mbox | patch
Permalink /patch/230642/
State New
Headers show

Comments

Kuo-Jung Su - March 25, 2013, 12:09 p.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.

Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
---
 hw/arm/Makefile.objs    |    1 +
 hw/arm/ftplat_a369soc.c |   11 ++
 hw/ftintc020.c          |  307 +++++++++++++++++++++++++++++++++++++++++++++++
 hw/ftintc020.h          |   55 +++++++++
 4 files changed, 374 insertions(+)
 create mode 100644 hw/ftintc020.c
 create mode 100644 hw/ftintc020.h

Patch

diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
index 09217c6..7cdd831 100644
--- a/hw/arm/Makefile.objs
+++ b/hw/arm/Makefile.objs
@@ -24,6 +24,7 @@  obj-y += framebuffer.o
 obj-y += strongarm.o
 obj-y += imx_serial.o imx_ccm.o imx_timer.o imx_avic.o
 obj-$(CONFIG_KVM) += kvm/arm_gic.o
+obj-y += ftintc020.o
 
 obj-y := $(addprefix ../,$(obj-y))
 
diff --git a/hw/arm/ftplat_a369soc.c b/hw/arm/ftplat_a369soc.c
index 624b549..331ec2a 100644
--- a/hw/arm/ftplat_a369soc.c
+++ b/hw/arm/ftplat_a369soc.c
@@ -51,6 +51,8 @@  static void a369soc_reset(DeviceState *ds)
 
 static void a369soc_chip_init(FaradaySoCState *s)
 {
+    int i;
+    DeviceState *ds;
     DriveInfo *dinfo;
 
     /* Remappable Memory Region Init */
@@ -84,6 +86,15 @@  static void a369soc_chip_init(FaradaySoCState *s)
     memory_region_add_subregion(s->rmr, s->rom_base,
                 sysbus_mmio_get_region(SYS_BUS_DEVICE(s->rom), 0));
 
+    /* Interrupt Controller */
+    ds = sysbus_create_varargs("ftintc020", 0x90100000,
+                               s->cpu_pic[ARM_PIC_CPU_IRQ],
+                               s->cpu_pic[ARM_PIC_CPU_FIQ], NULL);
+    s->pic = g_new0(qemu_irq, 64);
+    for (i = 0; i < 64; ++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,
diff --git a/hw/ftintc020.c b/hw/ftintc020.c
new file mode 100644
index 0000000..9aaff1d
--- /dev/null
+++ b/hw/ftintc020.c
@@ -0,0 +1,307 @@ 
+/*
+ * 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 "qemu/bitops.h"
+#include "hw/ftintc020.h"
+
+#define TYPE_FTINTC020  "ftintc020"
+
+#define CFG_REGSIZE     (0x100 / 4)
+
+typedef struct Ftintc020State {
+    /*< private >*/
+    SysBusDevice parent;
+
+    /*< public >*/
+    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);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+    /* Enable IC memory-mapped registers access.  */
+    memory_region_init_io(&s->iomem,
+                          &mmio_ops,
+                          s,
+                          TYPE_FTINTC020,
+                          0x1000);
+    sysbus_init_mmio(sbd, &s->iomem);
+
+    qdev_init_gpio_in(&sbd->qdev, ftintc020_set_irq, 64);
+    sysbus_init_irq(sbd, &s->irq);
+    sysbus_init_irq(sbd, &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)
diff --git a/hw/ftintc020.h b/hw/ftintc020.h
new file mode 100644
index 0000000..e354497
--- /dev/null
+++ b/hw/ftintc020.h
@@ -0,0 +1,55 @@ 
+/*
+ * 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
+
+/* 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