/* * ARM MPCore internal peripheral emulation. * * Copyright (c) 2006-2007 CodeSourcery. * Written by Paul Brook * * This code is licenced under the GPL. */ #include "sysbus.h" #include "qemu-timer.h" #define MPCORE_PRIV_BASE 0x10100000 #define NCPU 4 /* ??? The MPCore TRM says the on-chip controller has 224 external IRQ lines (+ 32 internal). However my test chip only exposes/reports 32. More importantly Linux falls over if more than 32 are present! */ #define GIC_NIRQ 64 static inline int gic_get_current_cpu(void) { return cpu_single_env->cpu_index; } #include "arm_gic.c" /* MPCore private memory region. */ typedef struct { uint32_t count; uint32_t load; uint32_t control; uint32_t status; uint32_t old_status; int64_t tick; QEMUTimer *timer; struct mpcore_priv_state *mpcore; int id; /* Encodes both timer/watchdog and CPU. */ } mpcore_timer_state; typedef struct mpcore_priv_state { gic_state gic; uint32_t scu_control; int iomemtype; mpcore_timer_state timer[8]; } mpcore_priv_state; /* Per-CPU Timers. */ static inline void mpcore_timer_update_irq(mpcore_timer_state *s) { if (s->status & ~s->old_status) { gic_set_pending_private(&s->mpcore->gic, s->id >> 1, 29 + (s->id & 1)); } s->old_status = s->status; } /* Return conversion factor from mpcore timer ticks to qemu timer ticks. */ static inline uint32_t mpcore_timer_scale(mpcore_timer_state *s) { return (((s->control >> 8) & 0xff) + 1) * 10; } static void mpcore_timer_reload(mpcore_timer_state *s, int restart) { if (s->count == 0) return; if (restart) s->tick = qemu_get_clock(vm_clock); s->tick += (int64_t)s->count * mpcore_timer_scale(s); qemu_mod_timer(s->timer, s->tick); } static void mpcore_timer_tick(void *opaque) { mpcore_timer_state *s = (mpcore_timer_state *)opaque; s->status = 1; if (s->control & 2) { s->count = s->load; mpcore_timer_reload(s, 0); } else { s->count = 0; } mpcore_timer_update_irq(s); } static uint32_t mpcore_timer_read(mpcore_timer_state *s, int offset) { int64_t val; switch (offset) { case 0: /* Load */ return s->load; /* Fall through. */ case 4: /* Counter. */ if (((s->control & 1) == 0) || (s->count == 0)) return 0; /* Slow and ugly, but hopefully won't happen too often. */ val = s->tick - qemu_get_clock(vm_clock); val /= mpcore_timer_scale(s); if (val < 0) val = 0; return val; case 8: /* Control. */ return s->control; case 12: /* Interrupt status. */ return s->status; default: return 0; } } static void mpcore_timer_write(mpcore_timer_state *s, int offset, uint32_t value) { int64_t old; switch (offset) { case 0: /* Load */ s->load = value; /* Fall through. */ case 4: /* Counter. */ if ((s->control & 1) && s->count) { /* Cancel the previous timer. */ qemu_del_timer(s->timer); } s->count = value; if (s->control & 1) { mpcore_timer_reload(s, 1); } break; case 8: /* Control. */ old = s->control; s->control = value; if (((old & 1) == 0) && (value & 1)) { if (s->count == 0 && (s->control & 2)) s->count = s->load; mpcore_timer_reload(s, 1); } break; case 12: /* Interrupt status. */ s->status &= ~value; mpcore_timer_update_irq(s); break; } } static void mpcore_timer_init(mpcore_priv_state *mpcore, mpcore_timer_state *s, int id) { s->id = id; s->mpcore = mpcore; s->timer = qemu_new_timer(vm_clock, mpcore_timer_tick, s); } /* Per-CPU private memory mapped IO. */ static uint32_t mpcore_priv_read(void *opaque, a_target_phys_addr offset) { mpcore_priv_state *s = (mpcore_priv_state *)opaque; int id; offset &= 0xfff; if (offset < 0x100) { /* SCU */ switch (offset) { case 0x00: /* Control. */ return s->scu_control; case 0x04: /* Configuration. */ return 0xf3; case 0x08: /* CPU status. */ return 0; case 0x0c: /* Invalidate all. */ return 0; default: goto bad_reg; } } else if (offset < 0x600) { /* Interrupt controller. */ if (offset < 0x200) { id = gic_get_current_cpu(); } else { id = (offset - 0x200) >> 8; } return gic_cpu_read(&s->gic, id, offset & 0xff); } else if (offset < 0xb00) { /* Timers. */ if (offset < 0x700) { id = gic_get_current_cpu(); } else { id = (offset - 0x700) >> 8; } id <<= 1; if (offset & 0x20) id++; return mpcore_timer_read(&s->timer[id], offset & 0xf); } bad_reg: hw_error("mpcore_priv_read: Bad offset %x\n", (int)offset); return 0; } static void mpcore_priv_write(void *opaque, a_target_phys_addr offset, uint32_t value) { mpcore_priv_state *s = (mpcore_priv_state *)opaque; int id; offset &= 0xfff; if (offset < 0x100) { /* SCU */ switch (offset) { case 0: /* Control register. */ s->scu_control = value & 1; break; case 0x0c: /* Invalidate all. */ /* This is a no-op as cache is not emulated. */ break; default: goto bad_reg; } } else if (offset < 0x600) { /* Interrupt controller. */ if (offset < 0x200) { id = gic_get_current_cpu(); } else { id = (offset - 0x200) >> 8; } gic_cpu_write(&s->gic, id, offset & 0xff, value); } else if (offset < 0xb00) { /* Timers. */ if (offset < 0x700) { id = gic_get_current_cpu(); } else { id = (offset - 0x700) >> 8; } id <<= 1; if (offset & 0x20) id++; mpcore_timer_write(&s->timer[id], offset & 0xf, value); return; } return; bad_reg: hw_error("mpcore_priv_read: Bad offset %x\n", (int)offset); } static CPUReadMemoryFunc * const mpcore_priv_readfn[] = { mpcore_priv_read, mpcore_priv_read, mpcore_priv_read }; static CPUWriteMemoryFunc * const mpcore_priv_writefn[] = { mpcore_priv_write, mpcore_priv_write, mpcore_priv_write }; static void mpcore_priv_map(SysBusDevice *dev, a_target_phys_addr base) { mpcore_priv_state *s = FROM_SYSBUSGIC(mpcore_priv_state, dev); cpu_register_physical_memory(base, 0x1000, s->iomemtype); cpu_register_physical_memory(base + 0x1000, 0x1000, s->gic.iomemtype); } static int mpcore_priv_init(SysBusDevice *dev) { mpcore_priv_state *s = FROM_SYSBUSGIC(mpcore_priv_state, dev); int i; gic_init(&s->gic); s->iomemtype = cpu_register_io_memory(mpcore_priv_readfn, mpcore_priv_writefn, s); sysbus_init_mmio_cb(dev, 0x2000, mpcore_priv_map); for (i = 0; i < 8; i++) { mpcore_timer_init(s, &s->timer[i], i); } return 0; } /* Dummy PIC to route IRQ lines. The baseboard has 4 independent IRQ controllers. The output of these, plus some of the raw input lines are fed into a single SMP-aware interrupt controller on the CPU. */ typedef struct { SysBusDevice busdev; qemu_irq cpuic[32]; qemu_irq rvic[4][64]; } mpcore_rirq_state; /* Map baseboard IRQs onto CPU IRQ lines. */ static const int mpcore_irq_map[32] = { -1, -1, -1, -1, 1, 2, -1, -1, -1, -1, 6, -1, 4, 5, -1, -1, -1, 14, 15, 0, 7, 8, -1, -1, -1, -1, -1, -1, 9, 3, -1, -1, }; static void mpcore_rirq_set_irq(void *opaque, int irq, int level) { mpcore_rirq_state *s = (mpcore_rirq_state *)opaque; int i; for (i = 0; i < 4; i++) { qemu_set_irq(s->rvic[i][irq], level); } if (irq < 32) { irq = mpcore_irq_map[irq]; if (irq >= 0) { qemu_set_irq(s->cpuic[irq], level); } } } static int realview_mpcore_init(SysBusDevice *dev) { mpcore_rirq_state *s = FROM_SYSBUS(mpcore_rirq_state, dev); DeviceState *gic; DeviceState *priv; int n; int i; priv = sysbus_create_simple("arm11mpcore_priv", MPCORE_PRIV_BASE, NULL); sysbus_pass_irq(dev, sysbus_from_qdev(priv)); for (i = 0; i < 32; i++) { s->cpuic[i] = qdev_get_gpio_in(priv, i); } /* ??? IRQ routing is hardcoded to "normal" mode. */ for (n = 0; n < 4; n++) { gic = sysbus_create_simple("realview_gic", 0x10040000 + n * 0x10000, s->cpuic[10 + n]); for (i = 0; i < 64; i++) { s->rvic[n][i] = qdev_get_gpio_in(gic, i); } } qdev_init_gpio_in(&dev->qdev, mpcore_rirq_set_irq, 64); return 0; } static void mpcore_register_devices(void) { sysbus_register_dev("realview_mpcore", sizeof(mpcore_rirq_state), realview_mpcore_init); sysbus_register_dev("arm11mpcore_priv", sizeof(mpcore_priv_state), mpcore_priv_init); } device_init(mpcore_register_devices)