blob: ccdda89dab510594065a55e58e252a4f06c68a9b [file] [log] [blame]
/*
* TI OMAP interrupt controller emulation.
*
* Copyright (C) 2006-2008 Andrzej Zaborowski <balrog@zabor.org>
* Copyright (C) 2007-2008 Nokia Corporation
*
* 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 or
* (at your option) version 3 of the License.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "hw/hw.h"
#include "hw/arm/omap.h"
#include "hw/sysbus.h"
#include "qemu/error-report.h"
#include "qapi/error.h"
/* Interrupt Handlers */
struct omap_intr_handler_bank_s {
uint32_t irqs;
uint32_t inputs;
uint32_t mask;
uint32_t fiq;
uint32_t sens_edge;
uint32_t swi;
unsigned char priority[32];
};
#define TYPE_OMAP_INTC "common-omap-intc"
#define OMAP_INTC(obj) \
OBJECT_CHECK(struct omap_intr_handler_s, (obj), TYPE_OMAP_INTC)
struct omap_intr_handler_s {
SysBusDevice parent_obj;
qemu_irq *pins;
qemu_irq parent_intr[2];
MemoryRegion mmio;
void *iclk;
void *fclk;
unsigned char nbanks;
int level_only;
uint32_t size;
uint8_t revision;
/* state */
uint32_t new_agr[2];
int sir_intr[2];
int autoidle;
uint32_t mask;
struct omap_intr_handler_bank_s bank[3];
};
static void omap_inth_sir_update(struct omap_intr_handler_s *s, int is_fiq)
{
int i, j, sir_intr, p_intr, p;
uint32_t level;
sir_intr = 0;
p_intr = 255;
/* Find the interrupt line with the highest dynamic priority.
* Note: 0 denotes the hightest priority.
* If all interrupts have the same priority, the default order is IRQ_N,
* IRQ_N-1,...,IRQ_0. */
for (j = 0; j < s->nbanks; ++j) {
level = s->bank[j].irqs & ~s->bank[j].mask &
(is_fiq ? s->bank[j].fiq : ~s->bank[j].fiq);
while (level != 0) {
i = ctz32(level);
p = s->bank[j].priority[i];
if (p <= p_intr) {
p_intr = p;
sir_intr = 32 * j + i;
}
level &= level - 1;
}
}
s->sir_intr[is_fiq] = sir_intr;
}
static inline void omap_inth_update(struct omap_intr_handler_s *s, int is_fiq)
{
int i;
uint32_t has_intr = 0;
for (i = 0; i < s->nbanks; ++i)
has_intr |= s->bank[i].irqs & ~s->bank[i].mask &
(is_fiq ? s->bank[i].fiq : ~s->bank[i].fiq);
if (s->new_agr[is_fiq] & has_intr & s->mask) {
s->new_agr[is_fiq] = 0;
omap_inth_sir_update(s, is_fiq);
qemu_set_irq(s->parent_intr[is_fiq], 1);
}
}
#define INT_FALLING_EDGE 0
#define INT_LOW_LEVEL 1
static void omap_set_intr(void *opaque, int irq, int req)
{
struct omap_intr_handler_s *ih = (struct omap_intr_handler_s *) opaque;
uint32_t rise;
struct omap_intr_handler_bank_s *bank = &ih->bank[irq >> 5];
int n = irq & 31;
if (req) {
rise = ~bank->irqs & (1 << n);
if (~bank->sens_edge & (1 << n))
rise &= ~bank->inputs;
bank->inputs |= (1 << n);
if (rise) {
bank->irqs |= rise;
omap_inth_update(ih, 0);
omap_inth_update(ih, 1);
}
} else {
rise = bank->sens_edge & bank->irqs & (1 << n);
bank->irqs &= ~rise;
bank->inputs &= ~(1 << n);
}
}
/* Simplified version with no edge detection */
static void omap_set_intr_noedge(void *opaque, int irq, int req)
{
struct omap_intr_handler_s *ih = (struct omap_intr_handler_s *) opaque;
uint32_t rise;
struct omap_intr_handler_bank_s *bank = &ih->bank[irq >> 5];
int n = irq & 31;
if (req) {
rise = ~bank->inputs & (1 << n);
if (rise) {
bank->irqs |= bank->inputs |= rise;
omap_inth_update(ih, 0);
omap_inth_update(ih, 1);
}
} else
bank->irqs = (bank->inputs &= ~(1 << n)) | bank->swi;
}
static uint64_t omap_inth_read(void *opaque, hwaddr addr,
unsigned size)
{
struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque;
int i, offset = addr;
int bank_no = offset >> 8;
int line_no;
struct omap_intr_handler_bank_s *bank = &s->bank[bank_no];
offset &= 0xff;
switch (offset) {
case 0x00: /* ITR */
return bank->irqs;
case 0x04: /* MIR */
return bank->mask;
case 0x10: /* SIR_IRQ_CODE */
case 0x14: /* SIR_FIQ_CODE */
if (bank_no != 0)
break;
line_no = s->sir_intr[(offset - 0x10) >> 2];
bank = &s->bank[line_no >> 5];
i = line_no & 31;
if (((bank->sens_edge >> i) & 1) == INT_FALLING_EDGE)
bank->irqs &= ~(1 << i);
return line_no;
case 0x18: /* CONTROL_REG */
if (bank_no != 0)
break;
return 0;
case 0x1c: /* ILR0 */
case 0x20: /* ILR1 */
case 0x24: /* ILR2 */
case 0x28: /* ILR3 */
case 0x2c: /* ILR4 */
case 0x30: /* ILR5 */
case 0x34: /* ILR6 */
case 0x38: /* ILR7 */
case 0x3c: /* ILR8 */
case 0x40: /* ILR9 */
case 0x44: /* ILR10 */
case 0x48: /* ILR11 */
case 0x4c: /* ILR12 */
case 0x50: /* ILR13 */
case 0x54: /* ILR14 */
case 0x58: /* ILR15 */
case 0x5c: /* ILR16 */
case 0x60: /* ILR17 */
case 0x64: /* ILR18 */
case 0x68: /* ILR19 */
case 0x6c: /* ILR20 */
case 0x70: /* ILR21 */
case 0x74: /* ILR22 */
case 0x78: /* ILR23 */
case 0x7c: /* ILR24 */
case 0x80: /* ILR25 */
case 0x84: /* ILR26 */
case 0x88: /* ILR27 */
case 0x8c: /* ILR28 */
case 0x90: /* ILR29 */
case 0x94: /* ILR30 */
case 0x98: /* ILR31 */
i = (offset - 0x1c) >> 2;
return (bank->priority[i] << 2) |
(((bank->sens_edge >> i) & 1) << 1) |
((bank->fiq >> i) & 1);
case 0x9c: /* ISR */
return 0x00000000;
}
OMAP_BAD_REG(addr);
return 0;
}
static void omap_inth_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque;
int i, offset = addr;
int bank_no = offset >> 8;
struct omap_intr_handler_bank_s *bank = &s->bank[bank_no];
offset &= 0xff;
switch (offset) {
case 0x00: /* ITR */
/* Important: ignore the clearing if the IRQ is level-triggered and
the input bit is 1 */
bank->irqs &= value | (bank->inputs & bank->sens_edge);
return;
case 0x04: /* MIR */
bank->mask = value;
omap_inth_update(s, 0);
omap_inth_update(s, 1);
return;
case 0x10: /* SIR_IRQ_CODE */
case 0x14: /* SIR_FIQ_CODE */
OMAP_RO_REG(addr);
break;
case 0x18: /* CONTROL_REG */
if (bank_no != 0)
break;
if (value & 2) {
qemu_set_irq(s->parent_intr[1], 0);
s->new_agr[1] = ~0;
omap_inth_update(s, 1);
}
if (value & 1) {
qemu_set_irq(s->parent_intr[0], 0);
s->new_agr[0] = ~0;
omap_inth_update(s, 0);
}
return;
case 0x1c: /* ILR0 */
case 0x20: /* ILR1 */
case 0x24: /* ILR2 */
case 0x28: /* ILR3 */
case 0x2c: /* ILR4 */
case 0x30: /* ILR5 */
case 0x34: /* ILR6 */
case 0x38: /* ILR7 */
case 0x3c: /* ILR8 */
case 0x40: /* ILR9 */
case 0x44: /* ILR10 */
case 0x48: /* ILR11 */
case 0x4c: /* ILR12 */
case 0x50: /* ILR13 */
case 0x54: /* ILR14 */
case 0x58: /* ILR15 */
case 0x5c: /* ILR16 */
case 0x60: /* ILR17 */
case 0x64: /* ILR18 */
case 0x68: /* ILR19 */
case 0x6c: /* ILR20 */
case 0x70: /* ILR21 */
case 0x74: /* ILR22 */
case 0x78: /* ILR23 */
case 0x7c: /* ILR24 */
case 0x80: /* ILR25 */
case 0x84: /* ILR26 */
case 0x88: /* ILR27 */
case 0x8c: /* ILR28 */
case 0x90: /* ILR29 */
case 0x94: /* ILR30 */
case 0x98: /* ILR31 */
i = (offset - 0x1c) >> 2;
bank->priority[i] = (value >> 2) & 0x1f;
bank->sens_edge &= ~(1 << i);
bank->sens_edge |= ((value >> 1) & 1) << i;
bank->fiq &= ~(1 << i);
bank->fiq |= (value & 1) << i;
return;
case 0x9c: /* ISR */
for (i = 0; i < 32; i ++)
if (value & (1 << i)) {
omap_set_intr(s, 32 * bank_no + i, 1);
return;
}
return;
}
OMAP_BAD_REG(addr);
}
static const MemoryRegionOps omap_inth_mem_ops = {
.read = omap_inth_read,
.write = omap_inth_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static void omap_inth_reset(DeviceState *dev)
{
struct omap_intr_handler_s *s = OMAP_INTC(dev);
int i;
for (i = 0; i < s->nbanks; ++i){
s->bank[i].irqs = 0x00000000;
s->bank[i].mask = 0xffffffff;
s->bank[i].sens_edge = 0x00000000;
s->bank[i].fiq = 0x00000000;
s->bank[i].inputs = 0x00000000;
s->bank[i].swi = 0x00000000;
memset(s->bank[i].priority, 0, sizeof(s->bank[i].priority));
if (s->level_only)
s->bank[i].sens_edge = 0xffffffff;
}
s->new_agr[0] = ~0;
s->new_agr[1] = ~0;
s->sir_intr[0] = 0;
s->sir_intr[1] = 0;
s->autoidle = 0;
s->mask = ~0;
qemu_set_irq(s->parent_intr[0], 0);
qemu_set_irq(s->parent_intr[1], 0);
}
static void omap_intc_init(Object *obj)
{
DeviceState *dev = DEVICE(obj);
struct omap_intr_handler_s *s = OMAP_INTC(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
s->nbanks = 1;
sysbus_init_irq(sbd, &s->parent_intr[0]);
sysbus_init_irq(sbd, &s->parent_intr[1]);
qdev_init_gpio_in(dev, omap_set_intr, s->nbanks * 32);
memory_region_init_io(&s->mmio, obj, &omap_inth_mem_ops, s,
"omap-intc", s->size);
sysbus_init_mmio(sbd, &s->mmio);
}
static void omap_intc_realize(DeviceState *dev, Error **errp)
{
struct omap_intr_handler_s *s = OMAP_INTC(dev);
if (!s->iclk) {
error_setg(errp, "omap-intc: clk not connected");
}
}
static Property omap_intc_properties[] = {
DEFINE_PROP_UINT32("size", struct omap_intr_handler_s, size, 0x100),
DEFINE_PROP_PTR("clk", struct omap_intr_handler_s, iclk),
DEFINE_PROP_END_OF_LIST(),
};
static void omap_intc_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->reset = omap_inth_reset;
dc->props = omap_intc_properties;
/* Reason: pointer property "clk" */
dc->user_creatable = false;
dc->realize = omap_intc_realize;
}
static const TypeInfo omap_intc_info = {
.name = "omap-intc",
.parent = TYPE_OMAP_INTC,
.instance_init = omap_intc_init,
.class_init = omap_intc_class_init,
};
static uint64_t omap2_inth_read(void *opaque, hwaddr addr,
unsigned size)
{
struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque;
int offset = addr;
int bank_no, line_no;
struct omap_intr_handler_bank_s *bank = NULL;
if ((offset & 0xf80) == 0x80) {
bank_no = (offset & 0x60) >> 5;
if (bank_no < s->nbanks) {
offset &= ~0x60;
bank = &s->bank[bank_no];
} else {
OMAP_BAD_REG(addr);
return 0;
}
}
switch (offset) {
case 0x00: /* INTC_REVISION */
return s->revision;
case 0x10: /* INTC_SYSCONFIG */
return (s->autoidle >> 2) & 1;
case 0x14: /* INTC_SYSSTATUS */
return 1; /* RESETDONE */
case 0x40: /* INTC_SIR_IRQ */
return s->sir_intr[0];
case 0x44: /* INTC_SIR_FIQ */
return s->sir_intr[1];
case 0x48: /* INTC_CONTROL */
return (!s->mask) << 2; /* GLOBALMASK */
case 0x4c: /* INTC_PROTECTION */
return 0;
case 0x50: /* INTC_IDLE */
return s->autoidle & 3;
/* Per-bank registers */
case 0x80: /* INTC_ITR */
return bank->inputs;
case 0x84: /* INTC_MIR */
return bank->mask;
case 0x88: /* INTC_MIR_CLEAR */
case 0x8c: /* INTC_MIR_SET */
return 0;
case 0x90: /* INTC_ISR_SET */
return bank->swi;
case 0x94: /* INTC_ISR_CLEAR */
return 0;
case 0x98: /* INTC_PENDING_IRQ */
return bank->irqs & ~bank->mask & ~bank->fiq;
case 0x9c: /* INTC_PENDING_FIQ */
return bank->irqs & ~bank->mask & bank->fiq;
/* Per-line registers */
case 0x100 ... 0x300: /* INTC_ILR */
bank_no = (offset - 0x100) >> 7;
if (bank_no > s->nbanks)
break;
bank = &s->bank[bank_no];
line_no = (offset & 0x7f) >> 2;
return (bank->priority[line_no] << 2) |
((bank->fiq >> line_no) & 1);
}
OMAP_BAD_REG(addr);
return 0;
}
static void omap2_inth_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque;
int offset = addr;
int bank_no, line_no;
struct omap_intr_handler_bank_s *bank = NULL;
if ((offset & 0xf80) == 0x80) {
bank_no = (offset & 0x60) >> 5;
if (bank_no < s->nbanks) {
offset &= ~0x60;
bank = &s->bank[bank_no];
} else {
OMAP_BAD_REG(addr);
return;
}
}
switch (offset) {
case 0x10: /* INTC_SYSCONFIG */
s->autoidle &= 4;
s->autoidle |= (value & 1) << 2;
if (value & 2) { /* SOFTRESET */
omap_inth_reset(DEVICE(s));
}
return;
case 0x48: /* INTC_CONTROL */
s->mask = (value & 4) ? 0 : ~0; /* GLOBALMASK */
if (value & 2) { /* NEWFIQAGR */
qemu_set_irq(s->parent_intr[1], 0);
s->new_agr[1] = ~0;
omap_inth_update(s, 1);
}
if (value & 1) { /* NEWIRQAGR */
qemu_set_irq(s->parent_intr[0], 0);
s->new_agr[0] = ~0;
omap_inth_update(s, 0);
}
return;
case 0x4c: /* INTC_PROTECTION */
/* TODO: Make a bitmap (or sizeof(char)map) of access privileges
* for every register, see Chapter 3 and 4 for privileged mode. */
if (value & 1)
fprintf(stderr, "%s: protection mode enable attempt\n",
__FUNCTION__);
return;
case 0x50: /* INTC_IDLE */
s->autoidle &= ~3;
s->autoidle |= value & 3;
return;
/* Per-bank registers */
case 0x84: /* INTC_MIR */
bank->mask = value;
omap_inth_update(s, 0);
omap_inth_update(s, 1);
return;
case 0x88: /* INTC_MIR_CLEAR */
bank->mask &= ~value;
omap_inth_update(s, 0);
omap_inth_update(s, 1);
return;
case 0x8c: /* INTC_MIR_SET */
bank->mask |= value;
return;
case 0x90: /* INTC_ISR_SET */
bank->irqs |= bank->swi |= value;
omap_inth_update(s, 0);
omap_inth_update(s, 1);
return;
case 0x94: /* INTC_ISR_CLEAR */
bank->swi &= ~value;
bank->irqs = bank->swi & bank->inputs;
return;
/* Per-line registers */
case 0x100 ... 0x300: /* INTC_ILR */
bank_no = (offset - 0x100) >> 7;
if (bank_no > s->nbanks)
break;
bank = &s->bank[bank_no];
line_no = (offset & 0x7f) >> 2;
bank->priority[line_no] = (value >> 2) & 0x3f;
bank->fiq &= ~(1 << line_no);
bank->fiq |= (value & 1) << line_no;
return;
case 0x00: /* INTC_REVISION */
case 0x14: /* INTC_SYSSTATUS */
case 0x40: /* INTC_SIR_IRQ */
case 0x44: /* INTC_SIR_FIQ */
case 0x80: /* INTC_ITR */
case 0x98: /* INTC_PENDING_IRQ */
case 0x9c: /* INTC_PENDING_FIQ */
OMAP_RO_REG(addr);
return;
}
OMAP_BAD_REG(addr);
}
static const MemoryRegionOps omap2_inth_mem_ops = {
.read = omap2_inth_read,
.write = omap2_inth_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static void omap2_intc_init(Object *obj)
{
DeviceState *dev = DEVICE(obj);
struct omap_intr_handler_s *s = OMAP_INTC(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
s->level_only = 1;
s->nbanks = 3;
sysbus_init_irq(sbd, &s->parent_intr[0]);
sysbus_init_irq(sbd, &s->parent_intr[1]);
qdev_init_gpio_in(dev, omap_set_intr_noedge, s->nbanks * 32);
memory_region_init_io(&s->mmio, obj, &omap2_inth_mem_ops, s,
"omap2-intc", 0x1000);
sysbus_init_mmio(sbd, &s->mmio);
}
static void omap2_intc_realize(DeviceState *dev, Error **errp)
{
struct omap_intr_handler_s *s = OMAP_INTC(dev);
if (!s->iclk) {
error_setg(errp, "omap2-intc: iclk not connected");
return;
}
if (!s->fclk) {
error_setg(errp, "omap2-intc: fclk not connected");
return;
}
}
static Property omap2_intc_properties[] = {
DEFINE_PROP_UINT8("revision", struct omap_intr_handler_s,
revision, 0x21),
DEFINE_PROP_PTR("iclk", struct omap_intr_handler_s, iclk),
DEFINE_PROP_PTR("fclk", struct omap_intr_handler_s, fclk),
DEFINE_PROP_END_OF_LIST(),
};
static void omap2_intc_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->reset = omap_inth_reset;
dc->props = omap2_intc_properties;
/* Reason: pointer property "iclk", "fclk" */
dc->user_creatable = false;
dc->realize = omap2_intc_realize;
}
static const TypeInfo omap2_intc_info = {
.name = "omap2-intc",
.parent = TYPE_OMAP_INTC,
.instance_init = omap2_intc_init,
.class_init = omap2_intc_class_init,
};
static const TypeInfo omap_intc_type_info = {
.name = TYPE_OMAP_INTC,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(struct omap_intr_handler_s),
.abstract = true,
};
static void omap_intc_register_types(void)
{
type_register_static(&omap_intc_type_info);
type_register_static(&omap_intc_info);
type_register_static(&omap2_intc_info);
}
type_init(omap_intc_register_types)