0c69996e22
Implement a basic ASPEED VIC device model for the AST2400 SoC[1], with enough functionality to boot an aspeed_defconfig Linux kernel. The model implements the 'new' (revised) register set: While the hardware exposes both the new and legacy register sets, accesses to the model's legacy register set will not be serviced (however the access will be logged). [1] http://www.aspeedtech.com/products.php?fPath=20&rId=376 Signed-off-by: Andrew Jeffery <andrew@aj.id.au> Message-id: 1458096317-25223-3-git-send-email-andrew@aj.id.au Reviewed-by: Peter Maydell <peter.maydell@linaro.org> Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
340 lines
10 KiB
C
340 lines
10 KiB
C
/*
|
|
* ASPEED Interrupt Controller (New)
|
|
*
|
|
* Andrew Jeffery <andrew@aj.id.au>
|
|
*
|
|
* Copyright 2015, 2016 IBM Corp.
|
|
*
|
|
* This code is licensed under the GPL version 2 or later. See
|
|
* the COPYING file in the top-level directory.
|
|
*/
|
|
|
|
/* The hardware exposes two register sets, a legacy set and a 'new' set. The
|
|
* model implements the 'new' register set, and logs warnings on accesses to
|
|
* the legacy IO space.
|
|
*
|
|
* The hardware uses 32bit registers to manage 51 IRQs, with low and high
|
|
* registers for each conceptual register. The device model's implementation
|
|
* uses 64bit data types to store both low and high register values (in the one
|
|
* member), but must cope with access offset values in multiples of 4 passed to
|
|
* the callbacks. As such the read() and write() implementations process the
|
|
* provided offset to understand whether the access is requesting the lower or
|
|
* upper 32 bits of the 64bit member.
|
|
*
|
|
* Additionally, the "Interrupt Enable", "Edge Status" and "Software Interrupt"
|
|
* fields have separate "enable"/"status" and "clear" registers, where set bits
|
|
* are written to one or the other to change state (avoiding a
|
|
* read-modify-write sequence).
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include <inttypes.h>
|
|
#include "hw/intc/aspeed_vic.h"
|
|
#include "qemu/bitops.h"
|
|
#include "trace.h"
|
|
|
|
#define AVIC_NEW_BASE_OFFSET 0x80
|
|
|
|
#define AVIC_L_MASK 0xFFFFFFFFU
|
|
#define AVIC_H_MASK 0x0007FFFFU
|
|
#define AVIC_EVENT_W_MASK (0x78000ULL << 32)
|
|
|
|
static void aspeed_vic_update(AspeedVICState *s)
|
|
{
|
|
uint64_t new = (s->raw & s->enable);
|
|
uint64_t flags;
|
|
|
|
flags = new & s->select;
|
|
trace_aspeed_vic_update_fiq(!!flags);
|
|
qemu_set_irq(s->fiq, !!flags);
|
|
|
|
flags = new & ~s->select;
|
|
trace_aspeed_vic_update_irq(!!flags);
|
|
qemu_set_irq(s->irq, !!flags);
|
|
}
|
|
|
|
static void aspeed_vic_set_irq(void *opaque, int irq, int level)
|
|
{
|
|
uint64_t irq_mask;
|
|
bool raise;
|
|
AspeedVICState *s = (AspeedVICState *)opaque;
|
|
|
|
if (irq > ASPEED_VIC_NR_IRQS) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n",
|
|
__func__, irq);
|
|
return;
|
|
}
|
|
|
|
trace_aspeed_vic_set_irq(irq, level);
|
|
|
|
irq_mask = BIT(irq);
|
|
if (s->sense & irq_mask) {
|
|
/* level-triggered */
|
|
if (s->event & irq_mask) {
|
|
/* high-sensitive */
|
|
raise = level;
|
|
} else {
|
|
/* low-sensitive */
|
|
raise = !level;
|
|
}
|
|
s->raw = deposit64(s->raw, irq, 1, raise);
|
|
} else {
|
|
uint64_t old_level = s->level & irq_mask;
|
|
|
|
/* edge-triggered */
|
|
if (s->dual_edge & irq_mask) {
|
|
raise = (!!old_level) != (!!level);
|
|
} else {
|
|
if (s->event & irq_mask) {
|
|
/* rising-sensitive */
|
|
raise = !old_level && level;
|
|
} else {
|
|
/* falling-sensitive */
|
|
raise = old_level && !level;
|
|
}
|
|
}
|
|
if (raise) {
|
|
s->raw = deposit64(s->raw, irq, 1, raise);
|
|
}
|
|
}
|
|
s->level = deposit64(s->level, irq, 1, level);
|
|
aspeed_vic_update(s);
|
|
}
|
|
|
|
static uint64_t aspeed_vic_read(void *opaque, hwaddr offset, unsigned size)
|
|
{
|
|
uint64_t val;
|
|
const bool high = !!(offset & 0x4);
|
|
hwaddr n_offset = (offset & ~0x4);
|
|
AspeedVICState *s = (AspeedVICState *)opaque;
|
|
|
|
if (offset < AVIC_NEW_BASE_OFFSET) {
|
|
qemu_log_mask(LOG_UNIMP, "%s: Ignoring read from legacy registers "
|
|
"at 0x%" HWADDR_PRIx "[%u]\n", __func__, offset, size);
|
|
return 0;
|
|
}
|
|
|
|
n_offset -= AVIC_NEW_BASE_OFFSET;
|
|
|
|
switch (n_offset) {
|
|
case 0x0: /* IRQ Status */
|
|
val = s->raw & ~s->select & s->enable;
|
|
break;
|
|
case 0x08: /* FIQ Status */
|
|
val = s->raw & s->select & s->enable;
|
|
break;
|
|
case 0x10: /* Raw Interrupt Status */
|
|
val = s->raw;
|
|
break;
|
|
case 0x18: /* Interrupt Selection */
|
|
val = s->select;
|
|
break;
|
|
case 0x20: /* Interrupt Enable */
|
|
val = s->enable;
|
|
break;
|
|
case 0x30: /* Software Interrupt */
|
|
val = s->trigger;
|
|
break;
|
|
case 0x40: /* Interrupt Sensitivity */
|
|
val = s->sense;
|
|
break;
|
|
case 0x48: /* Interrupt Both Edge Trigger Control */
|
|
val = s->dual_edge;
|
|
break;
|
|
case 0x50: /* Interrupt Event */
|
|
val = s->event;
|
|
break;
|
|
case 0x60: /* Edge Triggered Interrupt Status */
|
|
val = s->raw & ~s->sense;
|
|
break;
|
|
/* Illegal */
|
|
case 0x28: /* Interrupt Enable Clear */
|
|
case 0x38: /* Software Interrupt Clear */
|
|
case 0x58: /* Edge Triggered Interrupt Clear */
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"%s: Read of write-only register with offset 0x%"
|
|
HWADDR_PRIx "\n", __func__, offset);
|
|
val = 0;
|
|
break;
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"%s: Bad register at offset 0x%" HWADDR_PRIx "\n",
|
|
__func__, offset);
|
|
val = 0;
|
|
break;
|
|
}
|
|
if (high) {
|
|
val = extract64(val, 32, 19);
|
|
}
|
|
trace_aspeed_vic_read(offset, size, val);
|
|
return val;
|
|
}
|
|
|
|
static void aspeed_vic_write(void *opaque, hwaddr offset, uint64_t data,
|
|
unsigned size)
|
|
{
|
|
const bool high = !!(offset & 0x4);
|
|
hwaddr n_offset = (offset & ~0x4);
|
|
AspeedVICState *s = (AspeedVICState *)opaque;
|
|
|
|
if (offset < AVIC_NEW_BASE_OFFSET) {
|
|
qemu_log_mask(LOG_UNIMP,
|
|
"%s: Ignoring write to legacy registers at 0x%"
|
|
HWADDR_PRIx "[%u] <- 0x%" PRIx64 "\n", __func__, offset,
|
|
size, data);
|
|
return;
|
|
}
|
|
|
|
n_offset -= AVIC_NEW_BASE_OFFSET;
|
|
trace_aspeed_vic_write(offset, size, data);
|
|
|
|
/* Given we have members using separate enable/clear registers, deposit64()
|
|
* isn't quite the tool for the job. Instead, relocate the incoming bits to
|
|
* the required bit offset based on the provided access address
|
|
*/
|
|
if (high) {
|
|
data &= AVIC_H_MASK;
|
|
data <<= 32;
|
|
} else {
|
|
data &= AVIC_L_MASK;
|
|
}
|
|
|
|
switch (n_offset) {
|
|
case 0x18: /* Interrupt Selection */
|
|
/* Register has deposit64() semantics - overwrite requested 32 bits */
|
|
if (high) {
|
|
s->select &= AVIC_L_MASK;
|
|
} else {
|
|
s->select &= ((uint64_t) AVIC_H_MASK) << 32;
|
|
}
|
|
s->select |= data;
|
|
break;
|
|
case 0x20: /* Interrupt Enable */
|
|
s->enable |= data;
|
|
break;
|
|
case 0x28: /* Interrupt Enable Clear */
|
|
s->enable &= ~data;
|
|
break;
|
|
case 0x30: /* Software Interrupt */
|
|
qemu_log_mask(LOG_UNIMP, "%s: Software interrupts unavailable. "
|
|
"IRQs requested: 0x%016" PRIx64 "\n", __func__, data);
|
|
break;
|
|
case 0x38: /* Software Interrupt Clear */
|
|
qemu_log_mask(LOG_UNIMP, "%s: Software interrupts unavailable. "
|
|
"IRQs to be cleared: 0x%016" PRIx64 "\n", __func__, data);
|
|
break;
|
|
case 0x50: /* Interrupt Event */
|
|
/* Register has deposit64() semantics - overwrite the top four valid
|
|
* IRQ bits, as only the top four IRQs (GPIOs) can change their event
|
|
* type */
|
|
if (high) {
|
|
s->event &= ~AVIC_EVENT_W_MASK;
|
|
s->event |= (data & AVIC_EVENT_W_MASK);
|
|
} else {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"Ignoring invalid write to interrupt event register");
|
|
}
|
|
break;
|
|
case 0x58: /* Edge Triggered Interrupt Clear */
|
|
s->raw &= ~(data & ~s->sense);
|
|
break;
|
|
case 0x00: /* IRQ Status */
|
|
case 0x08: /* FIQ Status */
|
|
case 0x10: /* Raw Interrupt Status */
|
|
case 0x40: /* Interrupt Sensitivity */
|
|
case 0x48: /* Interrupt Both Edge Trigger Control */
|
|
case 0x60: /* Edge Triggered Interrupt Status */
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"%s: Write of read-only register with offset 0x%"
|
|
HWADDR_PRIx "\n", __func__, offset);
|
|
break;
|
|
|
|
default:
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"%s: Bad register at offset 0x%" HWADDR_PRIx "\n",
|
|
__func__, offset);
|
|
break;
|
|
}
|
|
aspeed_vic_update(s);
|
|
}
|
|
|
|
static const MemoryRegionOps aspeed_vic_ops = {
|
|
.read = aspeed_vic_read,
|
|
.write = aspeed_vic_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
.valid.min_access_size = 4,
|
|
.valid.max_access_size = 4,
|
|
.valid.unaligned = false,
|
|
};
|
|
|
|
static void aspeed_vic_reset(DeviceState *dev)
|
|
{
|
|
AspeedVICState *s = ASPEED_VIC(dev);
|
|
|
|
s->level = 0;
|
|
s->raw = 0;
|
|
s->select = 0;
|
|
s->enable = 0;
|
|
s->trigger = 0;
|
|
s->sense = 0x1F07FFF8FFFFULL;
|
|
s->dual_edge = 0xF800070000ULL;
|
|
s->event = 0x5F07FFF8FFFFULL;
|
|
}
|
|
|
|
#define AVIC_IO_REGION_SIZE 0x20000
|
|
|
|
static void aspeed_vic_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
|
|
AspeedVICState *s = ASPEED_VIC(dev);
|
|
|
|
memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_vic_ops, s,
|
|
TYPE_ASPEED_VIC, AVIC_IO_REGION_SIZE);
|
|
|
|
sysbus_init_mmio(sbd, &s->iomem);
|
|
|
|
qdev_init_gpio_in(dev, aspeed_vic_set_irq, ASPEED_VIC_NR_IRQS);
|
|
sysbus_init_irq(sbd, &s->irq);
|
|
sysbus_init_irq(sbd, &s->fiq);
|
|
}
|
|
|
|
static const VMStateDescription vmstate_aspeed_vic = {
|
|
.name = "aspeed.new-vic",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_UINT64(level, AspeedVICState),
|
|
VMSTATE_UINT64(raw, AspeedVICState),
|
|
VMSTATE_UINT64(select, AspeedVICState),
|
|
VMSTATE_UINT64(enable, AspeedVICState),
|
|
VMSTATE_UINT64(trigger, AspeedVICState),
|
|
VMSTATE_UINT64(sense, AspeedVICState),
|
|
VMSTATE_UINT64(dual_edge, AspeedVICState),
|
|
VMSTATE_UINT64(event, AspeedVICState),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static void aspeed_vic_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
dc->realize = aspeed_vic_realize;
|
|
dc->reset = aspeed_vic_reset;
|
|
dc->desc = "ASPEED Interrupt Controller (New)";
|
|
dc->vmsd = &vmstate_aspeed_vic;
|
|
}
|
|
|
|
static const TypeInfo aspeed_vic_info = {
|
|
.name = TYPE_ASPEED_VIC,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_size = sizeof(AspeedVICState),
|
|
.class_init = aspeed_vic_class_init,
|
|
};
|
|
|
|
static void aspeed_vic_register_types(void)
|
|
{
|
|
type_register_static(&aspeed_vic_info);
|
|
}
|
|
|
|
type_init(aspeed_vic_register_types);
|