diff options
author | pbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162> | 2007-11-11 00:04:49 +0000 |
---|---|---|
committer | pbrook <pbrook@c046a42c-6fe2-441c-8c8c-71466251a162> | 2007-11-11 00:04:49 +0000 |
commit | 9ee6e8bb853bdea7ef6c645a1a07aa55fd206aba (patch) | |
tree | 1cd430d3d9ac641c8550cfd8956dbcce1a4b9121 /hw | |
parent | ee4e83ed8ddc8dac572a0123398adf78b63014ae (diff) |
ARMv7 support.
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@3572 c046a42c-6fe2-441c-8c8c-71466251a162
Diffstat (limited to 'hw')
-rw-r--r-- | hw/arm_boot.c | 22 | ||||
-rw-r--r-- | hw/arm_gic.c | 460 | ||||
-rw-r--r-- | hw/arm_sysctl.c | 5 | ||||
-rw-r--r-- | hw/armv7m.c | 204 | ||||
-rw-r--r-- | hw/armv7m_nvic.c | 381 | ||||
-rw-r--r-- | hw/integratorcp.c | 4 | ||||
-rw-r--r-- | hw/mpcore.c | 323 | ||||
-rw-r--r-- | hw/pl011.c | 15 | ||||
-rw-r--r-- | hw/pl022.c | 264 | ||||
-rw-r--r-- | hw/pl061.c | 256 | ||||
-rw-r--r-- | hw/pxa2xx.c | 8 | ||||
-rw-r--r-- | hw/realview.c | 66 | ||||
-rw-r--r-- | hw/realview_gic.c | 64 | ||||
-rw-r--r-- | hw/ssd0303.c | 273 | ||||
-rw-r--r-- | hw/ssd0323.c | 267 | ||||
-rw-r--r-- | hw/stellaris.c | 1101 | ||||
-rw-r--r-- | hw/versatilepb.c | 8 |
17 files changed, 3522 insertions, 199 deletions
diff --git a/hw/arm_boot.c b/hw/arm_boot.c index 7a99b41751..8ef14ab4e4 100644 --- a/hw/arm_boot.c +++ b/hw/arm_boot.c @@ -1,7 +1,7 @@ /* * ARM kernel loader. * - * Copyright (c) 2006 CodeSourcery. + * Copyright (c) 2006-2007 CodeSourcery. * Written by Paul Brook * * This code is licenced under the GPL. @@ -24,6 +24,22 @@ static uint32_t bootloader[] = { 0 /* Kernel entry point. Set by integratorcp_init. */ }; +/* Entry point for secondary CPUs. Enable interrupt controller and + Issue WFI until start address is written to system controller. */ +static uint32_t smpboot[] = { + 0xe3a00201, /* mov r0, #0x10000000 */ + 0xe3800601, /* orr r0, r0, #0x001000000 */ + 0xe3a01001, /* mov r1, #1 */ + 0xe5801100, /* str r1, [r0, #0x100] */ + 0xe3a00201, /* mov r0, #0x10000000 */ + 0xe3800030, /* orr r0, #0x30 */ + 0xe320f003, /* wfi */ + 0xe5901000, /* ldr r1, [r0] */ + 0xe3110003, /* tst r1, #3 */ + 0x1afffffb, /* bne <wfi> */ + 0xe12fff11 /* bx r1 */ +}; + static void main_cpu_reset(void *opaque) { CPUState *env = opaque; @@ -33,6 +49,8 @@ static void main_cpu_reset(void *opaque) arm_load_kernel(env, env->ram_size, env->kernel_filename, env->kernel_cmdline, env->initrd_filename, env->board_id, env->loader_start); + + /* TODO: Reset secondary CPUs. */ } static void set_kernel_args(uint32_t ram_size, int initrd_size, @@ -211,6 +229,8 @@ void arm_load_kernel(CPUState *env, int ram_size, const char *kernel_filename, bootloader[6] = entry; for (n = 0; n < sizeof(bootloader) / 4; n++) stl_raw(phys_ram_base + (n * 4), bootloader[n]); + for (n = 0; n < sizeof(smpboot) / 4; n++) + stl_raw(phys_ram_base + ram_size + (n * 4), smpboot[n]); if (old_param) set_kernel_args_old(ram_size, initrd_size, kernel_cmdline, loader_start); diff --git a/hw/arm_gic.c b/hw/arm_gic.c index 8cd7182cff..774b79bf86 100644 --- a/hw/arm_gic.c +++ b/hw/arm_gic.c @@ -1,17 +1,15 @@ /* - * ARM AMBA Generic/Distributed Interrupt Controller + * ARM Generic/Distributed Interrupt Controller * - * Copyright (c) 2006 CodeSourcery. + * Copyright (c) 2006-2007 CodeSourcery. * Written by Paul Brook * * This code is licenced under the GPL. */ -/* TODO: Some variants of this controller can handle multiple CPUs. - Currently only single CPU operation is implemented. */ - -#include "vl.h" -#include "arm_pic.h" +/* This file contains implementation code for the RealView EB interrupt + controller, MPCore distributed interrupt controller and ARMv7-M + Nested Vectored Interrupt Controller. */ //#define DEBUG_GIC @@ -22,58 +20,84 @@ do { printf("arm_gic: " fmt , ##args); } while (0) #define DPRINTF(fmt, args...) do {} while(0) #endif -/* Distributed interrupt controller. */ - +#ifdef NVIC +static const uint8_t gic_id[] = +{ 0x00, 0xb0, 0x1b, 0x00, 0x0d, 0xe0, 0x05, 0xb1 }; +#define GIC_DIST_OFFSET 0 +/* The NVIC has 16 internal vectors. However these are not exposed + through the normal GIC interface. */ +#define GIC_BASE_IRQ 32 +#else static const uint8_t gic_id[] = { 0x90, 0x13, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; - -#define GIC_NIRQ 96 +#define GIC_DIST_OFFSET 0x1000 +#define GIC_BASE_IRQ 0 +#endif typedef struct gic_irq_state { + /* ??? The documentation seems to imply the enable bits are global, even + for per-cpu interrupts. This seems strange. */ unsigned enabled:1; - unsigned pending:1; - unsigned active:1; + unsigned pending:NCPU; + unsigned active:NCPU; unsigned level:1; - unsigned model:1; /* 0 = 1:N, 1 = N:N */ + unsigned model:1; /* 0 = N:N, 1 = 1:N */ unsigned trigger:1; /* nonzero = edge triggered. */ } gic_irq_state; +#define ALL_CPU_MASK ((1 << NCPU) - 1) + #define GIC_SET_ENABLED(irq) s->irq_state[irq].enabled = 1 #define GIC_CLEAR_ENABLED(irq) s->irq_state[irq].enabled = 0 #define GIC_TEST_ENABLED(irq) s->irq_state[irq].enabled -#define GIC_SET_PENDING(irq) s->irq_state[irq].pending = 1 -#define GIC_CLEAR_PENDING(irq) s->irq_state[irq].pending = 0 -#define GIC_TEST_PENDING(irq) s->irq_state[irq].pending -#define GIC_SET_ACTIVE(irq) s->irq_state[irq].active = 1 -#define GIC_CLEAR_ACTIVE(irq) s->irq_state[irq].active = 0 -#define GIC_TEST_ACTIVE(irq) s->irq_state[irq].active +#define GIC_SET_PENDING(irq, cm) s->irq_state[irq].pending |= (cm) +#define GIC_CLEAR_PENDING(irq, cm) s->irq_state[irq].pending &= ~(cm) +#define GIC_TEST_PENDING(irq, cm) ((s->irq_state[irq].pending & (cm)) != 0) +#define GIC_SET_ACTIVE(irq, cm) s->irq_state[irq].active |= (cm) +#define GIC_CLEAR_ACTIVE(irq, cm) s->irq_state[irq].active &= ~(cm) +#define GIC_TEST_ACTIVE(irq, cm) ((s->irq_state[irq].active & (cm)) != 0) #define GIC_SET_MODEL(irq) s->irq_state[irq].model = 1 #define GIC_CLEAR_MODEL(irq) s->irq_state[irq].model = 0 #define GIC_TEST_MODEL(irq) s->irq_state[irq].model -#define GIC_SET_LEVEL(irq) s->irq_state[irq].level = 1 -#define GIC_CLEAR_LEVEL(irq) s->irq_state[irq].level = 0 -#define GIC_TEST_LEVEL(irq) s->irq_state[irq].level +#define GIC_SET_LEVEL(irq, cm) s->irq_state[irq].level = (cm) +#define GIC_CLEAR_LEVEL(irq, cm) s->irq_state[irq].level &= ~(cm) +#define GIC_TEST_LEVEL(irq, cm) (s->irq_state[irq].level & (cm)) != 0 #define GIC_SET_TRIGGER(irq) s->irq_state[irq].trigger = 1 #define GIC_CLEAR_TRIGGER(irq) s->irq_state[irq].trigger = 0 #define GIC_TEST_TRIGGER(irq) s->irq_state[irq].trigger +#define GIC_GET_PRIORITY(irq, cpu) \ + (((irq) < 32) ? s->priority1[irq][cpu] : s->priority2[(irq) - 32]) +#ifdef NVIC +#define GIC_TARGET(irq) 1 +#else +#define GIC_TARGET(irq) s->irq_target[irq] +#endif typedef struct gic_state { uint32_t base; - qemu_irq parent_irq; + qemu_irq parent_irq[NCPU]; int enabled; - int cpu_enabled; + int cpu_enabled[NCPU]; gic_irq_state irq_state[GIC_NIRQ]; +#ifndef NVIC int irq_target[GIC_NIRQ]; - int priority[GIC_NIRQ]; - int last_active[GIC_NIRQ]; - - int priority_mask; - int running_irq; - int running_priority; - int current_pending; +#endif + int priority1[32][NCPU]; + int priority2[GIC_NIRQ - 32]; + int last_active[GIC_NIRQ][NCPU]; + + int priority_mask[NCPU]; + int running_irq[NCPU]; + int running_priority[NCPU]; + int current_pending[NCPU]; + + qemu_irq *in; +#ifdef NVIC + void *nvic; +#endif } gic_state; /* TODO: Many places that call this routine could be optimized. */ @@ -83,112 +107,136 @@ static void gic_update(gic_state *s) int best_irq; int best_prio; int irq; - - s->current_pending = 1023; - if (!s->enabled || !s->cpu_enabled) { - qemu_irq_lower(s->parent_irq); - return; - } - best_prio = 0x100; - best_irq = 1023; - for (irq = 0; irq < 96; irq++) { - if (GIC_TEST_ENABLED(irq) && GIC_TEST_PENDING(irq)) { - if (s->priority[irq] < best_prio) { - best_prio = s->priority[irq]; - best_irq = irq; + int level; + int cpu; + int cm; + + for (cpu = 0; cpu < NCPU; cpu++) { + cm = 1 << cpu; + s->current_pending[cpu] = 1023; + if (!s->enabled || !s->cpu_enabled[cpu]) { + qemu_irq_lower(s->parent_irq[cpu]); + return; + } + best_prio = 0x100; + best_irq = 1023; + for (irq = 0; irq < GIC_NIRQ; irq++) { + if (GIC_TEST_ENABLED(irq) && GIC_TEST_PENDING(irq, cm)) { + if (GIC_GET_PRIORITY(irq, cpu) < best_prio) { + best_prio = GIC_GET_PRIORITY(irq, cpu); + best_irq = irq; + } } } - } - if (best_prio > s->priority_mask) { - qemu_irq_lower(s->parent_irq); - } else { - s->current_pending = best_irq; - if (best_prio < s->running_priority) { - DPRINTF("Raised pending IRQ %d\n", best_irq); - qemu_irq_raise(s->parent_irq); + level = 0; + if (best_prio <= s->priority_mask[cpu]) { + s->current_pending[cpu] = best_irq; + if (best_prio < s->running_priority[cpu]) { + DPRINTF("Raised pending IRQ %d\n", best_irq); + level = 1; + } } + qemu_set_irq(s->parent_irq[cpu], level); } } +static void __attribute__((unused)) +gic_set_pending_private(gic_state *s, int cpu, int irq) +{ + int cm = 1 << cpu; + + if (GIC_TEST_PENDING(irq, cm)) + return; + + DPRINTF("Set %d pending cpu %d\n", irq, cpu); + GIC_SET_PENDING(irq, cm); + gic_update(s); +} + +/* Process a change in an external IRQ input. */ static void gic_set_irq(void *opaque, int irq, int level) { gic_state *s = (gic_state *)opaque; /* The first external input line is internal interrupt 32. */ irq += 32; - if (level == GIC_TEST_LEVEL(irq)) + if (level == GIC_TEST_LEVEL(irq, ALL_CPU_MASK)) return; if (level) { - GIC_SET_LEVEL(irq); + GIC_SET_LEVEL(irq, ALL_CPU_MASK); if (GIC_TEST_TRIGGER(irq) || GIC_TEST_ENABLED(irq)) { - DPRINTF("Set %d pending\n", irq); - GIC_SET_PENDING(irq); + DPRINTF("Set %d pending mask %x\n", irq, GIC_TARGET(irq)); + GIC_SET_PENDING(irq, GIC_TARGET(irq)); } } else { - GIC_CLEAR_LEVEL(irq); + GIC_CLEAR_LEVEL(irq, ALL_CPU_MASK); } gic_update(s); } -static void gic_set_running_irq(gic_state *s, int irq) +static void gic_set_running_irq(gic_state *s, int cpu, int irq) { - s->running_irq = irq; - if (irq == 1023) - s->running_priority = 0x100; - else - s->running_priority = s->priority[irq]; + s->running_irq[cpu] = irq; + if (irq == 1023) { + s->running_priority[cpu] = 0x100; + } else { + s->running_priority[cpu] = GIC_GET_PRIORITY(irq, cpu); + } gic_update(s); } -static uint32_t gic_acknowledge_irq(gic_state *s) +static uint32_t gic_acknowledge_irq(gic_state *s, int cpu) { int new_irq; - new_irq = s->current_pending; - if (new_irq == 1023 || s->priority[new_irq] >= s->running_priority) { + int cm = 1 << cpu; + new_irq = s->current_pending[cpu]; + if (new_irq == 1023 + || GIC_GET_PRIORITY(new_irq, cpu) >= s->running_priority[cpu]) { DPRINTF("ACK no pending IRQ\n"); return 1023; } - qemu_irq_lower(s->parent_irq); - s->last_active[new_irq] = s->running_irq; - /* For level triggered interrupts we clear the pending bit while - the interrupt is active. */ - GIC_CLEAR_PENDING(new_irq); - gic_set_running_irq(s, new_irq); + s->last_active[new_irq][cpu] = s->running_irq[cpu]; + /* Clear pending flags for both level and edge triggered interrupts. + Level triggered IRQs will be reasserted once they become inactive. */ + GIC_CLEAR_PENDING(new_irq, GIC_TEST_MODEL(new_irq) ? ALL_CPU_MASK : cm); + gic_set_running_irq(s, cpu, new_irq); DPRINTF("ACK %d\n", new_irq); return new_irq; } -static void gic_complete_irq(gic_state * s, int irq) +static void gic_complete_irq(gic_state * s, int cpu, int irq) { int update = 0; + int cm = 1 << cpu; DPRINTF("EOI %d\n", irq); - if (s->running_irq == 1023) + if (s->running_irq[cpu] == 1023) return; /* No active IRQ. */ if (irq != 1023) { /* Mark level triggered interrupts as pending if they are still raised. */ if (!GIC_TEST_TRIGGER(irq) && GIC_TEST_ENABLED(irq) - && GIC_TEST_LEVEL(irq)) { - GIC_SET_PENDING(irq); + && GIC_TEST_LEVEL(irq, cm) && (GIC_TARGET(irq) & cm) != 0) { + DPRINTF("Set %d pending mask %x\n", irq, cm); + GIC_SET_PENDING(irq, cm); update = 1; } } - if (irq != s->running_irq) { + if (irq != s->running_irq[cpu]) { /* Complete an IRQ that is not currently running. */ - int tmp = s->running_irq; - while (s->last_active[tmp] != 1023) { - if (s->last_active[tmp] == irq) { - s->last_active[tmp] = s->last_active[irq]; + int tmp = s->running_irq[cpu]; + while (s->last_active[tmp][cpu] != 1023) { + if (s->last_active[tmp][cpu] == irq) { + s->last_active[tmp][cpu] = s->last_active[irq][cpu]; break; } - tmp = s->last_active[tmp]; + tmp = s->last_active[tmp][cpu]; } if (update) { gic_update(s); } } else { /* Complete the current running IRQ. */ - gic_set_running_irq(s, s->last_active[s->running_irq]); + gic_set_running_irq(s, cpu, s->last_active[s->running_irq[cpu]][cpu]); } } @@ -198,15 +246,22 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset) uint32_t res; int irq; int i; + int cpu; + int cm; + int mask; - offset -= s->base + 0x1000; + cpu = gic_get_current_cpu(); + cm = 1 << cpu; + offset -= s->base + GIC_DIST_OFFSET; if (offset < 0x100) { +#ifndef NVIC if (offset == 0) return s->enabled; if (offset == 4) - return (GIC_NIRQ / 32) - 1; + return ((GIC_NIRQ / 32) - 1) | ((NCPU - 1) << 5); if (offset < 0x08) return 0; +#endif goto bad_reg; } else if (offset < 0x200) { /* Interrupt Set/Clear Enable. */ @@ -214,6 +269,7 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset) irq = (offset - 0x100) * 8; else irq = (offset - 0x180) * 8; + irq += GIC_BASE_IRQ; if (irq >= GIC_NIRQ) goto bad_reg; res = 0; @@ -228,40 +284,48 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset) irq = (offset - 0x200) * 8; else irq = (offset - 0x280) * 8; + irq += GIC_BASE_IRQ; if (irq >= GIC_NIRQ) goto bad_reg; res = 0; + mask = (irq < 32) ? cm : ALL_CPU_MASK; for (i = 0; i < 8; i++) { - if (GIC_TEST_PENDING(irq + i)) { + if (GIC_TEST_PENDING(irq + i, mask)) { res |= (1 << i); } } } else if (offset < 0x400) { /* Interrupt Active. */ - irq = (offset - 0x300) * 8; + irq = (offset - 0x300) * 8 + GIC_BASE_IRQ; if (irq >= GIC_NIRQ) goto bad_reg; res = 0; + mask = (irq < 32) ? cm : ALL_CPU_MASK; for (i = 0; i < 8; i++) { - if (GIC_TEST_ACTIVE(irq + i)) { + if (GIC_TEST_ACTIVE(irq + i, mask)) { res |= (1 << i); } } } else if (offset < 0x800) { /* Interrupt Priority. */ - irq = offset - 0x400; + irq = (offset - 0x400) + GIC_BASE_IRQ; if (irq >= GIC_NIRQ) goto bad_reg; - res = s->priority[irq]; + res = GIC_GET_PRIORITY(irq, cpu); +#ifndef NVIC } else if (offset < 0xc00) { /* Interrupt CPU Target. */ - irq = offset - 0x800; + irq = (offset - 0x800) + GIC_BASE_IRQ; if (irq >= GIC_NIRQ) goto bad_reg; - res = s->irq_target[irq]; + if (irq >= 29 && irq <= 31) { + res = cm; + } else { + res = GIC_TARGET(irq); + } } else if (offset < 0xf00) { /* Interrupt Configuration. */ - irq = (offset - 0xc00) * 2; + irq = (offset - 0xc00) * 2 + GIC_BASE_IRQ; if (irq >= GIC_NIRQ) goto bad_reg; res = 0; @@ -271,6 +335,7 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset) if (GIC_TEST_TRIGGER(irq + i)) res |= (2 << (i * 2)); } +#endif } else if (offset < 0xfe0) { goto bad_reg; } else /* offset >= 0xfe0 */ { @@ -282,7 +347,7 @@ static uint32_t gic_dist_readb(void *opaque, target_phys_addr_t offset) } return res; bad_reg: - cpu_abort (cpu_single_env, "gic_dist_readb: Bad offset %x\n", offset); + cpu_abort(cpu_single_env, "gic_dist_readb: Bad offset %x\n", (int)offset); return 0; } @@ -297,6 +362,13 @@ static uint32_t gic_dist_readw(void *opaque, target_phys_addr_t offset) static uint32_t gic_dist_readl(void *opaque, target_phys_addr_t offset) { uint32_t val; +#ifdef NVIC + gic_state *s = (gic_state *)opaque; + uint32_t addr; + addr = offset - s->base; + if (addr < 0x100 || addr > 0xd00) + return nvic_readl(s->nvic, addr); +#endif val = gic_dist_readw(opaque, offset); val |= gic_dist_readw(opaque, offset + 2) << 16; return val; @@ -308,9 +380,14 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset, gic_state *s = (gic_state *)opaque; int irq; int i; + int cpu; - offset -= s->base + 0x1000; + cpu = gic_get_current_cpu(); + offset -= s->base + GIC_DIST_OFFSET; if (offset < 0x100) { +#ifdef NVIC + goto bad_reg; +#else if (offset == 0) { s->enabled = (value & 1); DPRINTF("Distribution %sabled\n", s->enabled ? "En" : "Dis"); @@ -319,27 +396,36 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset, } else { goto bad_reg; } +#endif } else if (offset < 0x180) { /* Interrupt Set Enable. */ - irq = (offset - 0x100) * 8; + irq = (offset - 0x100) * 8 + GIC_BASE_IRQ; if (irq >= GIC_NIRQ) goto bad_reg; + if (irq < 16) + value = 0xff; for (i = 0; i < 8; i++) { if (value & (1 << i)) { + int mask = (irq < 32) ? (1 << cpu) : GIC_TARGET(irq); if (!GIC_TEST_ENABLED(irq + i)) DPRINTF("Enabled IRQ %d\n", irq + i); GIC_SET_ENABLED(irq + i); /* If a raised level triggered IRQ enabled then mark is as pending. */ - if (GIC_TEST_LEVEL(irq + i) && !GIC_TEST_TRIGGER(irq + i)) - GIC_SET_PENDING(irq + i); + if (GIC_TEST_LEVEL(irq + i, mask) + && !GIC_TEST_TRIGGER(irq + i)) { + DPRINTF("Set %d pending mask %x\n", irq + i, mask); + GIC_SET_PENDING(irq + i, mask); + } } } } else if (offset < 0x200) { /* Interrupt Clear Enable. */ - irq = (offset - 0x180) * 8; + irq = (offset - 0x180) * 8 + GIC_BASE_IRQ; if (irq >= GIC_NIRQ) goto bad_reg; + if (irq < 16) + value = 0; for (i = 0; i < 8; i++) { if (value & (1 << i)) { if (GIC_TEST_ENABLED(irq + i)) @@ -349,22 +435,28 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset, } } else if (offset < 0x280) { /* Interrupt Set Pending. */ - irq = (offset - 0x200) * 8; + irq = (offset - 0x200) * 8 + GIC_BASE_IRQ; if (irq >= GIC_NIRQ) goto bad_reg; + if (irq < 16) + irq = 0; + for (i = 0; i < 8; i++) { if (value & (1 << i)) { - GIC_SET_PENDING(irq + i); + GIC_SET_PENDING(irq + i, GIC_TARGET(irq)); } } } else if (offset < 0x300) { /* Interrupt Clear Pending. */ - irq = (offset - 0x280) * 8; + irq = (offset - 0x280) * 8 + GIC_BASE_IRQ; if (irq >= GIC_NIRQ) goto bad_reg; for (i = 0; i < 8; i++) { + /* ??? This currently clears the pending bit for all CPUs, even + for per-CPU interrupts. It's unclear whether this is the + corect behavior. */ if (value & (1 << i)) { - GIC_CLEAR_PENDING(irq + i); + GIC_CLEAR_PENDING(irq + i, ALL_CPU_MASK); } } } else if (offset < 0x400) { @@ -372,21 +464,32 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset, goto bad_reg; } else if (offset < 0x800) { /* Interrupt Priority. */ - irq = offset - 0x400; + irq = (offset - 0x400) + GIC_BASE_IRQ; if (irq >= GIC_NIRQ) goto bad_reg; - s->priority[irq] = value; + if (irq < 32) { + s->priority1[irq][cpu] = value; + } else { + s->priority2[irq - 32] = value; + } +#ifndef NVIC } else if (offset < 0xc00) { /* Interrupt CPU Target. */ - irq = offset - 0x800; + irq = (offset - 0x800) + GIC_BASE_IRQ; if (irq >= GIC_NIRQ) goto bad_reg; - s->irq_target[irq] = value; + if (irq < 29) + value = 0; + else if (irq < 32) + value = ALL_CPU_MASK; + s->irq_target[irq] = value & ALL_CPU_MASK; } else if (offset < 0xf00) { /* Interrupt Configuration. */ - irq = (offset - 0xc00) * 4; + irq = (offset - 0xc00) * 4 + GIC_BASE_IRQ; if (irq >= GIC_NIRQ) goto bad_reg; + if (irq < 32) + value |= 0xaa; for (i = 0; i < 4; i++) { if (value & (1 << (i * 2))) { GIC_SET_MODEL(irq + i); @@ -399,25 +502,20 @@ static void gic_dist_writeb(void *opaque, target_phys_addr_t offset, GIC_CLEAR_TRIGGER(irq + i); } } +#endif } else { - /* 0xf00 is only handled for word writes. */ + /* 0xf00 is only handled for 32-bit writes. */ goto bad_reg; } gic_update(s); return; bad_reg: - cpu_abort (cpu_single_env, "gic_dist_writeb: Bad offset %x\n", offset); + cpu_abort(cpu_single_env, "gic_dist_writeb: Bad offset %x\n", (int)offset); } static void gic_dist_writew(void *opaque, target_phys_addr_t offset, uint32_t value) { - gic_state *s = (gic_state *)opaque; - if (offset - s->base == 0xf00) { - GIC_SET_PENDING(value & 0x3ff); - gic_update(s); - return; - } gic_dist_writeb(opaque, offset, value & 0xff); gic_dist_writeb(opaque, offset + 1, value >> 8); } @@ -425,6 +523,41 @@ static void gic_dist_writew(void *opaque, target_phys_addr_t offset, static void gic_dist_writel(void *opaque, target_phys_addr_t offset, uint32_t value) { + gic_state *s = (gic_state *)opaque; +#ifdef NVIC + uint32_t addr; + addr = offset - s->base; + if (addr < 0x100 || (addr > 0xd00 && addr != 0xf00)) { + nvic_writel(s->nvic, addr, value); + return; + } +#endif + if (offset - s->base == GIC_DIST_OFFSET + 0xf00) { + int cpu; + int irq; + int mask; + + cpu = gic_get_current_cpu(); + irq = value & 0x3ff; + switch ((value >> 24) & 3) { + case 0: + mask = (value >> 16) & ALL_CPU_MASK; + break; + case 1: + mask = 1 << cpu; + break; + case 2: + mask = ALL_CPU_MASK ^ (1 << cpu); + break; + default: + DPRINTF("Bad Soft Int target filter\n"); + mask = ALL_CPU_MASK; + break; + } + GIC_SET_PENDING(irq, mask); + gic_update(s); + return; + } gic_dist_writew(opaque, offset, value & 0xffff); gic_dist_writew(opaque, offset + 2, value >> 16); } @@ -441,105 +574,100 @@ static CPUWriteMemoryFunc *gic_dist_writefn[] = { gic_dist_writel }; -static uint32_t gic_cpu_read(void *opaque, target_phys_addr_t offset) +#ifndef NVIC +static uint32_t gic_cpu_read(gic_state *s, int cpu, int offset) { - gic_state *s = (gic_state *)opaque; - offset -= s->base; switch (offset) { case 0x00: /* Control */ - return s->cpu_enabled; + return s->cpu_enabled[cpu]; case 0x04: /* Priority mask */ - return s->priority_mask; + return s->priority_mask[cpu]; case 0x08: /* Binary Point */ /* ??? Not implemented. */ return 0; case 0x0c: /* Acknowledge */ - return gic_acknowledge_irq(s); + return gic_acknowledge_irq(s, cpu); case 0x14: /* Runing Priority */ - return s->running_priority; + return s->running_priority[cpu]; case 0x18: /* Highest Pending Interrupt */ - return s->current_pending; + return s->current_pending[cpu]; default: - cpu_abort (cpu_single_env, "gic_cpu_read: Bad offset %x\n", offset); + cpu_abort(cpu_single_env, "gic_cpu_read: Bad offset %x\n", + (int)offset); return 0; } } -static void gic_cpu_write(void *opaque, target_phys_addr_t offset, - uint32_t value) +static void gic_cpu_write(gic_state *s, int cpu, int offset, uint32_t value) { - gic_state *s = (gic_state *)opaque; - offset -= s->base; switch (offset) { case 0x00: /* Control */ - s->cpu_enabled = (value & 1); + s->cpu_enabled[cpu] = (value & 1); DPRINTF("CPU %sabled\n", s->cpu_enabled ? "En" : "Dis"); break; case 0x04: /* Priority mask */ - s->priority_mask = (value & 0x3ff); + s->priority_mask[cpu] = (value & 0xff); break; case 0x08: /* Binary Point */ /* ??? Not implemented. */ break; case 0x10: /* End Of Interrupt */ - return gic_complete_irq(s, value & 0x3ff); + return gic_complete_irq(s, cpu, value & 0x3ff); default: - cpu_abort (cpu_single_env, "gic_cpu_write: Bad offset %x\n", offset); + cpu_abort(cpu_single_env, "gic_cpu_write: Bad offset %x\n", + (int)offset); return; } gic_update(s); } - -static CPUReadMemoryFunc *gic_cpu_readfn[] = { - gic_cpu_read, - gic_cpu_read, - gic_cpu_read -}; - -static CPUWriteMemoryFunc *gic_cpu_writefn[] = { - gic_cpu_write, - gic_cpu_write, - gic_cpu_write -}; +#endif static void gic_reset(gic_state *s) { int i; memset(s->irq_state, 0, GIC_NIRQ * sizeof(gic_irq_state)); - s->priority_mask = 0xf0; - s->current_pending = 1023; - s->running_irq = 1023; - s->running_priority = 0x100; + for (i = 0 ; i < NCPU; i++) { + s->priority_mask[i] = 0xf0; + s->current_pending[i] = 1023; + s->running_irq[i] = 1023; + s->running_priority[i] = 0x100; +#ifdef NVIC + /* The NVIC doesn't have per-cpu interfaces, so enable by default. */ + s->cpu_enabled[i] = 1; +#else + s->cpu_enabled[i] = 0; +#endif + } for (i = 0; i < 15; i++) { GIC_SET_ENABLED(i); GIC_SET_TRIGGER(i); } +#ifdef NVIC + /* The NVIC is always enabled. */ + s->enabled = 1; +#else s->enabled = 0; - s->cpu_enabled = 0; +#endif } -qemu_irq *arm_gic_init(uint32_t base, qemu_irq parent_irq) +static gic_state *gic_init(uint32_t base, qemu_irq *parent_irq) { gic_state *s; - qemu_irq *qi; int iomemtype; + int i; s = (gic_state *)qemu_mallocz(sizeof(gic_state)); if (!s) return NULL; - qi = qemu_allocate_irqs(gic_set_irq, s, GIC_NIRQ); - s->parent_irq = parent_irq; - if (base != 0xffffffff) { - iomemtype = cpu_register_io_memory(0, gic_cpu_readfn, - gic_cpu_writefn, s); - cpu_register_physical_memory(base, 0x00001000, iomemtype); - iomemtype = cpu_register_io_memory(0, gic_dist_readfn, - gic_dist_writefn, s); - cpu_register_physical_memory(base + 0x1000, 0x00001000, iomemtype); - s->base = base; - } else { - s->base = 0; + s->in = qemu_allocate_irqs(gic_set_irq, s, GIC_NIRQ); + for (i = 0; i < NCPU; i++) { + s->parent_irq[i] = parent_irq[i]; } + iomemtype = cpu_register_io_memory(0, gic_dist_readfn, + gic_dist_writefn, s); + cpu_register_physical_memory(base + GIC_DIST_OFFSET, 0x00001000, + iomemtype); + s->base = base; gic_reset(s); - return qi; + return s; } diff --git a/hw/arm_sysctl.c b/hw/arm_sysctl.c index 468a494dbf..e3179e2a91 100644 --- a/hw/arm_sysctl.c +++ b/hw/arm_sysctl.c @@ -1,7 +1,7 @@ /* * Status and system control registers for ARM RealView/Versatile boards. * - * Copyright (c) 2006 CodeSourcery. + * Copyright (c) 2006-2007 CodeSourcery. * Written by Paul Brook * * This code is licenced under the GPL. @@ -200,6 +200,9 @@ void arm_sysctl_init(uint32_t base, uint32_t sys_id) return; s->base = base; s->sys_id = sys_id; + /* The MPcore bootloader uses these flags to start secondary CPUs. + We don't use a bootloader, so do this here. */ + s->flags = 3; iomemtype = cpu_register_io_memory(0, arm_sysctl_readfn, arm_sysctl_writefn, s); cpu_register_physical_memory(base, 0x00001000, iomemtype); diff --git a/hw/armv7m.c b/hw/armv7m.c new file mode 100644 index 0000000000..f0a90e12d1 --- /dev/null +++ b/hw/armv7m.c @@ -0,0 +1,204 @@ +/* + * ARMV7M System emulation. + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "vl.h" + +/* Bitbanded IO. Each word corresponds to a single bit. */ + +/* Get the byte address of the real memory for a bitband acess. */ +static inline uint32_t bitband_addr(uint32_t addr) +{ + uint32_t res; + + res = addr & 0xe0000000; + res |= (addr & 0x1ffffff) >> 5; + return res; + +} + +static uint32_t bitband_readb(void *opaque, target_phys_addr_t offset) +{ + uint8_t v; + cpu_physical_memory_read(bitband_addr(offset), &v, 1); + return (v & (1 << ((offset >> 2) & 7))) != 0; +} + +static void bitband_writeb(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + uint32_t addr; + uint8_t mask; + uint8_t v; + addr = bitband_addr(offset); + mask = (1 << ((offset >> 2) & 7)); + cpu_physical_memory_read(addr, &v, 1); + if (value & 1) + v |= mask; + else + v &= ~mask; + cpu_physical_memory_write(addr, &v, 1); +} + +static uint32_t bitband_readw(void *opaque, target_phys_addr_t offset) +{ + uint32_t addr; + uint16_t mask; + uint16_t v; + addr = bitband_addr(offset) & ~1; + mask = (1 << ((offset >> 2) & 15)); + mask = tswap16(mask); + cpu_physical_memory_read(addr, (uint8_t *)&v, 2); + return (v & mask) != 0; +} + +static void bitband_writew(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + uint32_t addr; + uint16_t mask; + uint16_t v; + addr = bitband_addr(offset) & ~1; + mask = (1 << ((offset >> 2) & 15)); + mask = tswap16(mask); + cpu_physical_memory_read(addr, (uint8_t *)&v, 2); + if (value & 1) + v |= mask; + else + v &= ~mask; + cpu_physical_memory_write(addr, (uint8_t *)&v, 2); +} + +static uint32_t bitband_readl(void *opaque, target_phys_addr_t offset) +{ + uint32_t addr; + uint32_t mask; + uint32_t v; + addr = bitband_addr(offset) & ~3; + mask = (1 << ((offset >> 2) & 31)); + mask = tswap32(mask); + cpu_physical_memory_read(addr, (uint8_t *)&v, 4); + return (v & mask) != 0; +} + +static void bitband_writel(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + uint32_t addr; + uint32_t mask; + uint32_t v; + addr = bitband_addr(offset) & ~3; + mask = (1 << ((offset >> 2) & 31)); + mask = tswap32(mask); + cpu_physical_memory_read(addr, (uint8_t *)&v, 4); + if (value & 1) + v |= mask; + else + v &= ~mask; + cpu_physical_memory_write(addr, (uint8_t *)&v, 4); +} + +static CPUReadMemoryFunc *bitband_readfn[] = { + bitband_readb, + bitband_readw, + bitband_readl +}; + +static CPUWriteMemoryFunc *bitband_writefn[] = { + bitband_writeb, + bitband_writew, + bitband_writel +}; + +static void armv7m_bitband_init(void) +{ + int iomemtype; + + iomemtype = cpu_register_io_memory(0, bitband_readfn, bitband_writefn, + NULL); + cpu_register_physical_memory(0x22000000, 0x02000000, iomemtype); + cpu_register_physical_memory(0x42000000, 0x02000000, iomemtype); +} + +/* Board init. */ +/* Init CPU and memory for a v7-M based board. + flash_size and sram_size are in kb. + Returns the NVIC array. */ + +qemu_irq *armv7m_init(int flash_size, int sram_size, + const char *kernel_filename, const char *cpu_model) +{ + CPUState *env; + qemu_irq *pic; + uint32_t pc; + int image_size; + uint64_t entry; + uint64_t lowaddr; + + flash_size *= 1024; + sram_size *= 1024; + + if (!cpu_model) + cpu_model = "cortex-m3"; + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + +#if 0 + /* > 32Mb SRAM gets complicated because it overlaps the bitband area. + We don't have proper commandline options, so allocate half of memory + as SRAM, up to a maximum of 32Mb, and the rest as code. */ + if (ram_size > (512 + 32) * 1024 * 1024) + ram_size = (512 + 32) * 1024 * 1024; + sram_size = (ram_size / 2) & TARGET_PAGE_MASK; + if (sram_size > 32 * 1024 * 1024) + sram_size = 32 * 1024 * 1024; + code_size = ram_size - sram_size; +#endif + + /* Flash programming is done via the SCU, so pretend it is ROM. */ + cpu_register_physical_memory(0, flash_size, IO_MEM_ROM); + cpu_register_physical_memory(0x20000000, sram_size, + flash_size + IO_MEM_RAM); + armv7m_bitband_init(); + + pic = armv7m_nvic_init(env); + + image_size = load_elf(kernel_filename, 0, &entry, &lowaddr, NULL); + if (image_size < 0) { + image_size = load_image(kernel_filename, phys_ram_base); + lowaddr = 0; + } + if (image_size < 0) { + fprintf(stderr, "qemu: could not load kernel '%s'\n", + kernel_filename); + exit(1); + } + + /* If the image was loaded at address zero then assume it is a + regular ROM image and perform the normal CPU reset sequence. + Otherwise jump directly to the entry point. */ + if (lowaddr == 0) { + env->regs[13] = tswap32(*(uint32_t *)phys_ram_base); + pc = tswap32(*(uint32_t *)(phys_ram_base + 4)); + } else { + pc = entry; + } + env->thumb = pc & 1; + env->regs[15] = pc & ~1; + + /* Hack to map an additional page of ram at the top of the address + space. This stops qemu complaining about executing code outside RAM + when returning from an exception. */ + cpu_register_physical_memory(0xfffff000, 0x1000, IO_MEM_RAM + ram_size); + + return pic; +} + diff --git a/hw/armv7m_nvic.c b/hw/armv7m_nvic.c new file mode 100644 index 0000000000..d304082886 --- /dev/null +++ b/hw/armv7m_nvic.c @@ -0,0 +1,381 @@ +/* + * ARM Nested Vectored Interrupt Controller + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + * + * The ARMv7M System controller is fairly tightly tied in with the + * NVIC. Much of that is also implemented here. + */ + +#include "vl.h" +#include "arm_pic.h" + +#define GIC_NIRQ 64 +#define NCPU 1 +#define NVIC 1 + +/* Only a single "CPU" interface is present. */ +static inline int +gic_get_current_cpu(void) +{ + return 0; +} + +static uint32_t nvic_readl(void *opaque, uint32_t offset); +static void nvic_writel(void *opaque, uint32_t offset, uint32_t value); + +#include "arm_gic.c" + +typedef struct { + struct { + uint32_t control; + uint32_t reload; + int64_t tick; + QEMUTimer *timer; + } systick; + gic_state *gic; +} nvic_state; + +/* qemu timers run at 1GHz. We want something closer to 1MHz. */ +#define SYSTICK_SCALE 1000ULL + +#define SYSTICK_ENABLE (1 << 0) +#define SYSTICK_TICKINT (1 << 1) +#define SYSTICK_CLKSOURCE (1 << 2) +#define SYSTICK_COUNTFLAG (1 << 16) + +/* Conversion factor from qemu timer to SysTick frequencies. + QEMU uses a base of 1GHz, so these give 20MHz and 1MHz for core and + reference frequencies. */ + +static inline int64_t systick_scale(nvic_state *s) +{ + if (s->systick.control & SYSTICK_CLKSOURCE) + return 50; + else + return 1000; +} + +static void systick_reload(nvic_state *s, int reset) +{ + if (reset) + s->systick.tick = qemu_get_clock(vm_clock); + s->systick.tick += (s->systick.reload + 1) * systick_scale(s); + qemu_mod_timer(s->systick.timer, s->systick.tick); +} + +static void systick_timer_tick(void * opaque) +{ + nvic_state *s = (nvic_state *)opaque; + s->systick.control |= SYSTICK_COUNTFLAG; + if (s->systick.control & SYSTICK_TICKINT) { + /* Trigger the interrupt. */ + armv7m_nvic_set_pending(s, ARMV7M_EXCP_SYSTICK); + } + if (s->systick.reload == 0) { + s->systick.control &= ~SYSTICK_ENABLE; + } else { + systick_reload(s, 0); + } +} + +/* The external routines use the hardware vector numbering, ie. the first + IRQ is #16. The internal GIC routines use #32 as the first IRQ. */ +void armv7m_nvic_set_pending(void *opaque, int irq) +{ + nvic_state *s = (nvic_state *)opaque; + if (irq >= 16) + irq += 16; + gic_set_pending_private(s->gic, 0, irq); +} + +/* Make pending IRQ active. */ +int armv7m_nvic_acknowledge_irq(void *opaque) +{ + nvic_state *s = (nvic_state *)opaque; + uint32_t irq; + + irq = gic_acknowledge_irq(s->gic, 0); + if (irq == 1023) + cpu_abort(cpu_single_env, "Interrupt but no vector\n"); + if (irq >= 32) + irq -= 16; + return irq; +} + +void armv7m_nvic_complete_irq(void *opaque, int irq) +{ + nvic_state *s = (nvic_state *)opaque; + if (irq >= 16) + irq += 16; + gic_complete_irq(s->gic, 0, irq); +} + +static uint32_t nvic_readl(void *opaque, uint32_t offset) +{ + nvic_state *s = (nvic_state *)opaque; + uint32_t val; + int irq; + + switch (offset) { + case 4: /* Interrupt Control Type. */ + return (GIC_NIRQ / 32) - 1; + case 0x10: /* SysTick Control and Status. */ + val = s->systick.control; + s->systick.control &= ~SYSTICK_COUNTFLAG; + return val; + case 0x14: /* SysTick Reload Value. */ + return s->systick.reload; + case 0x18: /* SysTick Current Value. */ + { + int64_t t; + if ((s->systick.control & SYSTICK_ENABLE) == 0) + return 0; + t = qemu_get_clock(vm_clock); + if (t >= s->systick.tick) + return 0; + val = ((s->systick.tick - (t + 1)) / systick_scale(s)) + 1; + /* The interrupt in triggered when the timer reaches zero. + However the counter is not reloaded until the next clock + tick. This is a hack to return zero during the first tick. */ + if (val > s->systick.reload) + val = 0; + return val; + } + case 0x1c: /* SysTick Calibration Value. */ + return 10000; + case 0xd00: /* CPUID Base. */ + return cpu_single_env->cp15.c0_cpuid; + case 0xd04: /* Interrypt Control State. */ + /* VECTACTIVE */ + val = s->gic->running_irq[0]; + if (val == 1023) { + val = 0; + } else if (val >= 32) { + val -= 16; + } + /* RETTOBASE */ + if (s->gic->running_irq[0] == 1023 + || s->gic->last_active[s->gic->running_irq[0]][0] == 1023) { + val |= (1 << 11); + } + /* VECTPENDING */ + if (s->gic->current_pending[0] != 1023) + val |= (s->gic->current_pending[0] << 12); + /* ISRPENDING */ + for (irq = 32; irq < GIC_NIRQ; irq++) { + if (s->gic->irq_state[irq].pending) { + val |= (1 << 22); + break; + } + } + /* PENDSTSET */ + if (s->gic->irq_state[ARMV7M_EXCP_SYSTICK].pending) + val |= (1 << 26); + /* PENDSVSET */ + if (s->gic->irq_state[ARMV7M_EXCP_PENDSV].pending) + val |= (1 << 28); + /* NMIPENDSET */ + if (s->gic->irq_state[ARMV7M_EXCP_NMI].pending) + val |= (1 << 31); + return val; + case 0xd08: /* Vector Table Offset. */ + return cpu_single_env->v7m.vecbase; + case 0xd0c: /* Application Interrupt/Reset Control. */ + return 0xfa05000; + case 0xd10: /* System Control. */ + /* TODO: Implement SLEEPONEXIT. */ + return 0; + case 0xd14: /* Configuration Control. */ + /* TODO: Implement Configuration Control bits. */ + return 0; + case 0xd18: case 0xd1c: case 0xd20: /* System Handler Priority. */ + irq = offset - 0xd14; + val = 0; + val = s->gic->priority1[irq++][0]; + val = s->gic->priority1[irq++][0] << 8; + val = s->gic->priority1[irq++][0] << 16; + val = s->gic->priority1[irq][0] << 24; + return val; + case 0xd24: /* System Handler Status. */ + val = 0; + if (s->gic->irq_state[ARMV7M_EXCP_MEM].active) val |= (1 << 0); + if (s->gic->irq_state[ARMV7M_EXCP_BUS].active) val |= (1 << 1); + if (s->gic->irq_state[ARMV7M_EXCP_USAGE].active) val |= (1 << 3); + if (s->gic->irq_state[ARMV7M_EXCP_SVC].active) val |= (1 << 7); + if (s->gic->irq_state[ARMV7M_EXCP_DEBUG].active) val |= (1 << 8); + if (s->gic->irq_state[ARMV7M_EXCP_PENDSV].active) val |= (1 << 10); + if (s->gic->irq_state[ARMV7M_EXCP_SYSTICK].active) val |= (1 << 11); + if (s->gic->irq_state[ARMV7M_EXCP_USAGE].pending) val |= (1 << 12); + if (s->gic->irq_state[ARMV7M_EXCP_MEM].pending) val |= (1 << 13); + if (s->gic->irq_state[ARMV7M_EXCP_BUS].pending) val |= (1 << 14); + if (s->gic->irq_state[ARMV7M_EXCP_SVC].pending) val |= (1 << 15); + if (s->gic->irq_state[ARMV7M_EXCP_MEM].enabled) val |= (1 << 16); + if (s->gic->irq_state[ARMV7M_EXCP_BUS].enabled) val |= (1 << 17); + if (s->gic->irq_state[ARMV7M_EXCP_USAGE].enabled) val |= (1 << 18); + return val; + case 0xd28: /* Configurable Fault Status. */ + /* TODO: Implement Fault Status. */ + cpu_abort(cpu_single_env, + "Not implemented: Configurable Fault Status."); + return 0; + case 0xd2c: /* Hard Fault Status. */ + case 0xd30: /* Debug Fault Status. */ + case 0xd34: /* Mem Manage Address. */ + case 0xd38: /* Bus Fault Address. */ + case 0xd3c: /* Aux Fault Status. */ + /* TODO: Implement fault status registers. */ + goto bad_reg; + case 0xd40: /* PFR0. */ + return 0x00000030; + case 0xd44: /* PRF1. */ + return 0x00000200; + case 0xd48: /* DFR0. */ + return 0x00100000; + case 0xd4c: /* AFR0. */ + return 0x00000000; + case 0xd50: /* MMFR0. */ + return 0x00000030; + case 0xd54: /* MMFR1. */ + return 0x00000000; + case 0xd58: /* MMFR2. */ + return 0x00000000; + case 0xd5c: /* MMFR3. */ + return 0x00000000; + case 0xd60: /* ISAR0. */ + return 0x01141110; + case 0xd64: /* ISAR1. */ + return 0x02111000; + case 0xd68: /* ISAR2. */ + return 0x21112231; + case 0xd6c: /* ISAR3. */ + return 0x01111110; + case 0xd70: /* ISAR4. */ + return 0x01310102; + /* TODO: Implement debug registers. */ + default: + bad_reg: + cpu_abort(cpu_single_env, "NVIC: Bad read offset 0x%x\n", offset); + } +} + +static void nvic_writel(void *opaque, uint32_t offset, uint32_t value) +{ + nvic_state *s = (nvic_state *)opaque; + uint32_t oldval; + switch (offset) { + case 0x10: /* SysTick Control and Status. */ + oldval = s->systick.control; + s->systick.control &= 0xfffffff8; + s->systick.control |= value & 7; + if ((oldval ^ value) & SYSTICK_ENABLE) { + int64_t now = qemu_get_clock(vm_clock); + if (value & SYSTICK_ENABLE) { + if (s->systick.tick) { + s->systick.tick += now; + qemu_mod_timer(s->systick.timer, s->systick.tick); + } else { + systick_reload(s, 1); + } + } else { + qemu_del_timer(s->systick.timer); + s->systick.tick -= now; + if (s->systick.tick < 0) + s->systick.tick = 0; + } + } else if ((oldval ^ value) & SYSTICK_CLKSOURCE) { + /* This is a hack. Force the timer to be reloaded + when the reference clock is changed. */ + systick_reload(s, 1); + } + break; + case 0x14: /* SysTick Reload Value. */ + s->systick.reload = value; + break; + case 0x18: /* SysTick Current Value. Writes reload the timer. */ + systick_reload(s, 1); + s->systick.control &= ~SYSTICK_COUNTFLAG; + break; + case 0xd04: /* Interrupt Control State. */ + if (value & (1 << 31)) { + armv7m_nvic_set_pending(s, ARMV7M_EXCP_NMI); + } + if (value & (1 << 28)) { + armv7m_nvic_set_pending(s, ARMV7M_EXCP_PENDSV); + } else if (value & (1 << 27)) { + s->gic->irq_state[ARMV7M_EXCP_PENDSV].pending = 0; + gic_update(s->gic); + } + if (value & (1 << 26)) { + armv7m_nvic_set_pending(s, ARMV7M_EXCP_SYSTICK); + } else if (value & (1 << 25)) { + s->gic->irq_state[ARMV7M_EXCP_SYSTICK].pending = 0; + gic_update(s->gic); + } + break; + case 0xd08: /* Vector Table Offset. */ + cpu_single_env->v7m.vecbase = value & 0xffffff80; + break; + case 0xd0c: /* Application Interrupt/Reset Control. */ + if ((value >> 16) == 0x05fa) { + if (value & 2) { + cpu_abort(cpu_single_env, "VECTCLRACTIVE not implemented"); + } + if (value & 5) { + cpu_abort(cpu_single_env, "System reset"); + } + } + break; + case 0xd10: /* System Control. */ + case 0xd14: /* Configuration Control. */ + /* TODO: Implement control registers. */ + goto bad_reg; + case 0xd18: case 0xd1c: case 0xd20: /* System Handler Priority. */ + { + int irq; + irq = offset - 0xd14; + s->gic->priority1[irq++][0] = value & 0xff; + s->gic->priority1[irq++][0] = (value >> 8) & 0xff; + s->gic->priority1[irq++][0] = (value >> 16) & 0xff; + s->gic->priority1[irq][0] = (value >> 24) & 0xff; + gic_update(s->gic); + } + break; + case 0xd24: /* System Handler Control. */ + /* TODO: Real hardware allows you to set/clear the active bits + under some circumstances. We don't implement this. */ + s->gic->irq_state[ARMV7M_EXCP_MEM].enabled = (value & (1 << 16)) != 0; + s->gic->irq_state[ARMV7M_EXCP_BUS].enabled = (value & (1 << 17)) != 0; + s->gic->irq_state[ARMV7M_EXCP_USAGE].enabled = (value & (1 << 18)) != 0; + break; + case 0xd28: /* Configurable Fault Status. */ + case 0xd2c: /* Hard Fault Status. */ + case 0xd30: /* Debug Fault Status. */ + case 0xd34: /* Mem Manage Address. */ + case 0xd38: /* Bus Fault Address. */ + case 0xd3c: /* Aux Fault Status. */ + goto bad_reg; + default: + bad_reg: + cpu_abort(cpu_single_env, "NVIC: Bad write offset 0x%x\n", offset); + } +} + +qemu_irq *armv7m_nvic_init(CPUState *env) +{ + nvic_state *s; + qemu_irq *parent; + + parent = arm_pic_init_cpu(env); + s = (nvic_state *)qemu_mallocz(sizeof(nvic_state)); + s->gic = gic_init(0xe000e000, &parent[ARM_PIC_CPU_IRQ]); + s->gic->nvic = s; + s->systick.timer = qemu_new_timer(vm_clock, systick_timer_tick, s); + if (env->v7m.nvic) + cpu_abort(env, "CPU can only have one NVIC\n"); + env->v7m.nvic = s; + return s->gic->in; +} diff --git a/hw/integratorcp.c b/hw/integratorcp.c index 75315a8b99..31e7d7d0e6 100644 --- a/hw/integratorcp.c +++ b/hw/integratorcp.c @@ -497,8 +497,8 @@ static void integratorcp_init(int ram_size, int vga_ram_size, icp_pic_init(0xca000000, pic[26], NULL); icp_pit_init(0x13000000, pic, 5); pl031_init(0x15000000, pic[8]); - pl011_init(0x16000000, pic[1], serial_hds[0]); - pl011_init(0x17000000, pic[2], serial_hds[1]); + pl011_init(0x16000000, pic[1], serial_hds[0], PL011_ARM); + pl011_init(0x17000000, pic[2], serial_hds[1], PL011_ARM); icp_control_init(0xcb000000); pl050_init(0x18000000, pic[3], 0); pl050_init(0x19000000, pic[4], 1); diff --git a/hw/mpcore.c b/hw/mpcore.c new file mode 100644 index 0000000000..cc33208e04 --- /dev/null +++ b/hw/mpcore.c @@ -0,0 +1,323 @@ +/* + * ARM MPCore internal peripheral emulation. + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "vl.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; + 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; + } +} + +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, target_phys_addr_t 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: + cpu_abort(cpu_single_env, "mpcore_priv_read: Bad offset %x\n", + (int)offset); + return 0; +} + +static void mpcore_priv_write(void *opaque, target_phys_addr_t 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: + cpu_abort(cpu_single_env, "mpcore_priv_read: Bad offset %x\n", + (int)offset); +} + +static CPUReadMemoryFunc *mpcore_priv_readfn[] = { + mpcore_priv_read, + mpcore_priv_read, + mpcore_priv_read +}; + +static CPUWriteMemoryFunc *mpcore_priv_writefn[] = { + mpcore_priv_write, + mpcore_priv_write, + mpcore_priv_write +}; + + +static qemu_irq *mpcore_priv_init(uint32_t base, qemu_irq *pic_irq) +{ + mpcore_priv_state *s; + int iomemtype; + int i; + + s = (mpcore_priv_state *)qemu_mallocz(sizeof(mpcore_priv_state)); + if (!s) + return NULL; + s->gic = gic_init(base, pic_irq); + if (!s->gic) + return NULL; + iomemtype = cpu_register_io_memory(0, mpcore_priv_readfn, + mpcore_priv_writefn, s); + cpu_register_physical_memory(base, 0x00001000, iomemtype); + for (i = 0; i < 8; i++) { + mpcore_timer_init(s, &s->timer[i], i); + } + return s->gic->in; +} + +/* 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 { + qemu_irq *cpuic; + qemu_irq *rvic[4]; +} 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); + } + } +} + +qemu_irq *mpcore_irq_init(qemu_irq *cpu_irq) +{ + mpcore_rirq_state *s; + int n; + + /* ??? IRQ routing is hardcoded to "normal" mode. */ + s = qemu_mallocz(sizeof(mpcore_rirq_state)); + s->cpuic = mpcore_priv_init(MPCORE_PRIV_BASE, cpu_irq); + for (n = 0; n < 4; n++) { + s->rvic[n] = realview_gic_init(0x10040000 + n * 0x10000, + s->cpuic[10 + n]); + } + return qemu_allocate_irqs(mpcore_rirq_set_irq, s, 64); +} diff --git a/hw/pl011.c b/hw/pl011.c index df3349188e..9037554148 100644 --- a/hw/pl011.c +++ b/hw/pl011.c @@ -28,6 +28,7 @@ typedef struct { int read_trigger; CharDriverState *chr; qemu_irq irq; + enum pl011_type type; } pl011_state; #define PL011_INT_TX 0x20 @@ -38,8 +39,10 @@ typedef struct { #define PL011_FLAG_TXFF 0x20 #define PL011_FLAG_RXFE 0x10 -static const unsigned char pl011_id[] = -{ 0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; +static const unsigned char pl011_id[2][8] = { + { 0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }, /* PL011_ARM */ + { 0x11, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1 }, /* PL011_LUMINARY */ +}; static void pl011_update(pl011_state *s) { @@ -56,7 +59,7 @@ static uint32_t pl011_read(void *opaque, target_phys_addr_t offset) offset -= s->base; if (offset >= 0xfe0 && offset < 0x1000) { - return pl011_id[(offset - 0xfe0) >> 2]; + return pl011_id[s->type][(offset - 0xfe0) >> 2]; } switch (offset >> 2) { case 0: /* UARTDR */ @@ -137,6 +140,9 @@ static void pl011_write(void *opaque, target_phys_addr_t offset, case 1: /* UARTCR */ s->cr = value; break; + case 6: /* UARTFR */ + /* Writes to Flag register are ignored. */ + break; case 8: /* UARTUARTILPR */ s->ilpr = value; break; @@ -224,7 +230,7 @@ static CPUWriteMemoryFunc *pl011_writefn[] = { }; void pl011_init(uint32_t base, qemu_irq irq, - CharDriverState *chr) + CharDriverState *chr, enum pl011_type type) { int iomemtype; pl011_state *s; @@ -235,6 +241,7 @@ void pl011_init(uint32_t base, qemu_irq irq, cpu_register_physical_memory(base, 0x00001000, iomemtype); s->base = base; s->irq = irq; + s->type = type; s->chr = chr; s->read_trigger = 1; s->ifl = 0x12; diff --git a/hw/pl022.c b/hw/pl022.c new file mode 100644 index 0000000000..d7c735b7ce --- /dev/null +++ b/hw/pl022.c @@ -0,0 +1,264 @@ +/* + * Arm PrimeCell PL022 Synchronous Serial Port + * + * Copyright (c) 2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "vl.h" + +//#define DEBUG_PL022 1 + +#ifdef DEBUG_PL022 +#define DPRINTF(fmt, args...) \ +do { printf("pl022: " fmt , ##args); } while (0) +#define BADF(fmt, args...) \ +do { fprintf(stderr, "pl022: error: " fmt , ##args); exit(1);} while (0) +#else +#define DPRINTF(fmt, args...) do {} while(0) +#define BADF(fmt, args...) \ +do { fprintf(stderr, "pl022: error: " fmt , ##args);} while (0) +#endif + +#define PL022_CR1_LBM 0x01 +#define PL022_CR1_SSE 0x02 +#define PL022_CR1_MS 0x04 +#define PL022_CR1_SDO 0x08 + +#define PL022_SR_TFE 0x01 +#define PL022_SR_TNF 0x02 +#define PL022_SR_RNE 0x04 +#define PL022_SR_RFF 0x08 +#define PL022_SR_BSY 0x10 + +#define PL022_INT_ROR 0x01 +#define PL022_INT_RT 0x04 +#define PL022_INT_RX 0x04 +#define PL022_INT_TX 0x08 + +typedef struct { + uint32_t base; + uint32_t cr0; + uint32_t cr1; + uint32_t bitmask; + uint32_t sr; + uint32_t cpsr; + uint32_t is; + uint32_t im; + /* The FIFO head points to the next empty entry. */ + int tx_fifo_head; + int rx_fifo_head; + int tx_fifo_len; + int rx_fifo_len; + uint16_t tx_fifo[8]; + uint16_t rx_fifo[8]; + qemu_irq irq; + int (*xfer_cb)(void *, int); + void *opaque; +} pl022_state; + +static const unsigned char pl022_id[8] = + { 0x22, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; + +static void pl022_update(pl022_state *s) +{ + s->sr = 0; + if (s->tx_fifo_len == 0) + s->sr |= PL022_SR_TFE; + if (s->tx_fifo_len != 8) + s->sr |= PL022_SR_TNF; + if (s->rx_fifo_len != 0) + s->sr |= PL022_SR_RNE; + if (s->rx_fifo_len == 8) + s->sr |= PL022_SR_RFF; + if (s->tx_fifo_len) + s->sr |= PL022_SR_BSY; + s->is = 0; + if (s->rx_fifo_len >= 4) + s->is |= PL022_INT_RX; + if (s->tx_fifo_len <= 4) + s->is |= PL022_INT_TX; + + qemu_set_irq(s->irq, (s->is & s->im) != 0); +} + +static void pl022_xfer(pl022_state *s) +{ + int i; + int o; + int val; + + if ((s->cr1 & PL022_CR1_SSE) == 0) { + pl022_update(s); + DPRINTF("Disabled\n"); + return; + } + + DPRINTF("Maybe xfer %d/%d\n", s->tx_fifo_len, s->rx_fifo_len); + i = (s->tx_fifo_head - s->tx_fifo_len) & 7; + o = s->rx_fifo_head; + /* ??? We do not emulate the line speed. + This may break some applications. The are two problematic cases: + (a) A driver feeds data into the TX FIFO until it is full, + and only then drains the RX FIFO. On real hardware the CPU can + feed data fast enough that the RX fifo never gets chance to overflow. + (b) A driver transmits data, deliberately allowing the RX FIFO to + overflow because it ignores the RX data anyway. + + We choose to support (a) by stalling the transmit engine if it would + cause the RX FIFO to overflow. In practice much transmit-only code + falls into (a) because it flushes the RX FIFO to determine when + the transfer has completed. */ + while (s->tx_fifo_len && s->rx_fifo_len < 8) { + DPRINTF("xfer\n"); + val = s->tx_fifo[i]; + if (s->cr1 & PL022_CR1_LBM) { + /* Loopback mode. */ + } else if (s->xfer_cb) { + val = s->xfer_cb(s->opaque, val); + } else { + val = 0; + } + s->rx_fifo[o] = val & s->bitmask; + i = (i + 1) & 7; + o = (o + 1) & 7; + s->tx_fifo_len--; + s->rx_fifo_len++; + } + s->rx_fifo_head = o; + pl022_update(s); +} + +static uint32_t pl022_read(void *opaque, target_phys_addr_t offset) +{ + pl022_state *s = (pl022_state *)opaque; + int val; + + offset -= s->base; + if (offset >= 0xfe0 && offset < 0x1000) { + return pl022_id[(offset - 0xfe0) >> 2]; + } + switch (offset) { + case 0x00: /* CR0 */ + return s->cr0; + case 0x04: /* CR1 */ + return s->cr1; + case 0x08: /* DR */ + if (s->rx_fifo_len) { + val = s->rx_fifo[(s->rx_fifo_head - s->rx_fifo_len) & 7]; + DPRINTF("RX %02x\n", val); + s->rx_fifo_len--; + pl022_xfer(s); + } else { + val = 0; + } + return val; + case 0x0c: /* SR */ + return s->sr; + case 0x10: /* CPSR */ + return s->cpsr; + case 0x14: /* IMSC */ + return s->im; + case 0x18: /* RIS */ + return s->is; + case 0x1c: /* MIS */ + return s->im & s->is; + case 0x20: /* DMACR */ + /* Not implemented. */ + return 0; + default: + cpu_abort (cpu_single_env, "pl022_read: Bad offset %x\n", + (int)offset); + return 0; + } +} + +static void pl022_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + pl022_state *s = (pl022_state *)opaque; + + offset -= s->base; + switch (offset) { + case 0x00: /* CR0 */ + s->cr0 = value; + /* Clock rate and format are ignored. */ + s->bitmask = (1 << ((value & 15) + 1)) - 1; + break; + case 0x04: /* CR1 */ + s->cr1 = value; + if ((s->cr1 & (PL022_CR1_MS | PL022_CR1_SSE)) + == (PL022_CR1_MS | PL022_CR1_SSE)) { + BADF("SPI slave mode not implemented\n"); + } + pl022_xfer(s); + break; + case 0x08: /* DR */ + if (s->tx_fifo_len < 8) { + DPRINTF("TX %02x\n", value); + s->tx_fifo[s->tx_fifo_head] = value & s->bitmask; + s->tx_fifo_head = (s->tx_fifo_head + 1) & 7; + s->tx_fifo_len++; + pl022_xfer(s); + } + break; + case 0x10: /* CPSR */ + /* Prescaler. Ignored. */ + s->cpsr = value & 0xff; + break; + case 0x14: /* IMSC */ + s->im = value; + pl022_update(s); + break; + case 0x20: /* DMACR */ + if (value) + cpu_abort (cpu_single_env, "pl022: DMA not implemented\n"); + break; + default: + cpu_abort (cpu_single_env, "pl022_write: Bad offset %x\n", + (int)offset); + } +} + +static void pl022_reset(pl022_state *s) +{ + s->rx_fifo_len = 0; + s->tx_fifo_len = 0; + s->im = 0; + s->is = PL022_INT_TX; + s->sr = PL022_SR_TFE | PL022_SR_TNF; +} + +static CPUReadMemoryFunc *pl022_readfn[] = { + pl022_read, + pl022_read, + pl022_read +}; + +static CPUWriteMemoryFunc *pl022_writefn[] = { + pl022_write, + pl022_write, + pl022_write +}; + +void pl022_init(uint32_t base, qemu_irq irq, int (*xfer_cb)(void *, int), + void * opaque) +{ + int iomemtype; + pl022_state *s; + + s = (pl022_state *)qemu_mallocz(sizeof(pl022_state)); + iomemtype = cpu_register_io_memory(0, pl022_readfn, + pl022_writefn, s); + cpu_register_physical_memory(base, 0x00001000, iomemtype); + s->base = base; + s->irq = irq; + s->xfer_cb = xfer_cb; + s->opaque = opaque; + pl022_reset(s); + /* ??? Save/restore. */ +} + + diff --git a/hw/pl061.c b/hw/pl061.c new file mode 100644 index 0000000000..fa5004a96c --- /dev/null +++ b/hw/pl061.c @@ -0,0 +1,256 @@ +/* + * Arm PrimeCell PL061 General Purpose IO with additional + * Luminary Micro Stellaris bits. + * + * Copyright (c) 2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "vl.h" + +//#define DEBUG_PL061 1 + +#ifdef DEBUG_PL061 +#define DPRINTF(fmt, args...) \ +do { printf("pl061: " fmt , ##args); } while (0) +#define BADF(fmt, args...) \ +do { fprintf(stderr, "pl061: error: " fmt , ##args); exit(1);} while (0) +#else +#define DPRINTF(fmt, args...) do {} while(0) +#define BADF(fmt, args...) \ +do { fprintf(stderr, "pl061: error: " fmt , ##args);} while (0) +#endif + +static const uint8_t pl061_id[12] = + { 0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1 }; + +typedef struct { + uint32_t base; + int locked; + uint8_t data; + uint8_t old_data; + uint8_t dir; + uint8_t isense; + uint8_t ibe; + uint8_t iev; + uint8_t im; + uint8_t istate; + uint8_t afsel; + uint8_t dr2r; + uint8_t dr4r; + uint8_t dr8r; + uint8_t odr; + uint8_t pur; + uint8_t pdr; + uint8_t slr; + uint8_t den; + uint8_t cr; + qemu_irq irq; + qemu_irq out[8]; +} pl061_state; + +static void pl061_update(pl061_state *s) +{ + uint8_t changed; + uint8_t mask; + int i; + + changed = s->old_data ^ s->data; + if (!changed) + return; + + s->old_data = s->data; + for (i = 0; i < 8; i++) { + mask = 1 << i; + if ((changed & mask & s->dir) && s->out) { + DPRINTF("Set output %d = %d\n", i, (s->data & mask) != 0); + qemu_set_irq(s->out[i], (s->data & mask) != 0); + } + } + + /* FIXME: Implement input interrupts. */ +} + +static uint32_t pl061_read(void *opaque, target_phys_addr_t offset) +{ + pl061_state *s = (pl061_state *)opaque; + + offset -= s->base; + if (offset >= 0xfd0 && offset < 0x1000) { + return pl061_id[(offset - 0xfd0) >> 2]; + } + if (offset < 0x400) { + return s->data & (offset >> 2); + } + switch (offset) { + case 0x400: /* Direction */ + return s->dir; + case 0x404: /* Interrupt sense */ + return s->isense; + case 0x408: /* Interrupt both edges */ + return s->ibe; + case 0x40c: /* Interupt event */ + return s->iev; + case 0x410: /* Interrupt mask */ + return s->im; + case 0x414: /* Raw interrupt status */ + return s->istate; + case 0x418: /* Masked interrupt status */ + return s->istate | s->im; + case 0x420: /* Alternate function select */ + return s->afsel; + case 0x500: /* 2mA drive */ + return s->dr2r; + case 0x504: /* 4mA drive */ + return s->dr4r; + case 0x508: /* 8mA drive */ + return s->dr8r; + case 0x50c: /* Open drain */ + return s->odr; + case 0x510: /* Pull-up */ + return s->pur; + case 0x514: /* Pull-down */ + return s->pdr; + case 0x518: /* Slew rate control */ + return s->slr; + case 0x51c: /* Digital enable */ + return s->den; + case 0x520: /* Lock */ + return s->locked; + case 0x524: /* Commit */ + return s->cr; + default: + cpu_abort (cpu_single_env, "pl061_read: Bad offset %x\n", + (int)offset); + return 0; + } +} + +static void pl061_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + pl061_state *s = (pl061_state *)opaque; + uint8_t mask; + + offset -= s->base; + if (offset < 0x400) { + mask = (offset >> 2) & s->dir; + s->data = (s->data & ~mask) | (value & mask); + pl061_update(s); + return; + } + switch (offset) { + case 0x400: /* Direction */ + s->dir = value; + break; + case 0x404: /* Interrupt sense */ + s->isense = value; + break; + case 0x408: /* Interrupt both edges */ + s->ibe = value; + break; + case 0x40c: /* Interupt event */ + s->iev = value; + break; + case 0x410: /* Interrupt mask */ + s->im = value; + break; + case 0x41c: /* Interrupt clear */ + s->istate &= ~value; + break; + case 0x420: /* Alternate function select */ + mask = s->cr; + s->afsel = (s->afsel & ~mask) | (value & mask); + break; + case 0x500: /* 2mA drive */ + s->dr2r = value; + break; + case 0x504: /* 4mA drive */ + s->dr4r = value; + break; + case 0x508: /* 8mA drive */ + s->dr8r = value; + break; + case 0x50c: /* Open drain */ + s->odr = value; + break; + case 0x510: /* Pull-up */ + s->pur = value; + break; + case 0x514: /* Pull-down */ + s->pdr = value; + break; + case 0x518: /* Slew rate control */ + s->slr = value; + break; + case 0x51c: /* Digital enable */ + s->den = value; + break; + case 0x520: /* Lock */ + s->locked = (value != 0xacce551); + break; + case 0x524: /* Commit */ + if (!s->locked) + s->cr = value; + break; + default: + cpu_abort (cpu_single_env, "pl061_write: Bad offset %x\n", + (int)offset); + } + pl061_update(s); +} + +static void pl061_reset(pl061_state *s) +{ + s->locked = 1; + s->cr = 0xff; +} + +void pl061_set_irq(void * opaque, int irq, int level) +{ + pl061_state *s = (pl061_state *)opaque; + uint8_t mask; + + mask = 1 << irq; + if ((s->dir & mask) == 0) { + s->data &= ~mask; + if (level) + s->data |= mask; + pl061_update(s); + } +} + +static CPUReadMemoryFunc *pl061_readfn[] = { + pl061_read, + pl061_read, + pl061_read +}; + +static CPUWriteMemoryFunc *pl061_writefn[] = { + pl061_write, + pl061_write, + pl061_write +}; + +/* Returns an array of inputs. */ +qemu_irq *pl061_init(uint32_t base, qemu_irq irq, qemu_irq **out) +{ + int iomemtype; + pl061_state *s; + + s = (pl061_state *)qemu_mallocz(sizeof(pl061_state)); + iomemtype = cpu_register_io_memory(0, pl061_readfn, + pl061_writefn, s); + cpu_register_physical_memory(base, 0x00001000, iomemtype); + s->base = base; + s->irq = irq; + pl061_reset(s); + if (out) + *out = s->out; + + /* ??? Save/restore. */ + return qemu_allocate_irqs(pl061_set_irq, s, 8); +} + diff --git a/hw/pxa2xx.c b/hw/pxa2xx.c index ebaff1320a..3c10839022 100644 --- a/hw/pxa2xx.c +++ b/hw/pxa2xx.c @@ -297,7 +297,7 @@ static void pxa2xx_clkpwr_write(void *opaque, int op2, int reg, int crm, ARM_CPU_MODE_SVC | CPSR_A | CPSR_F | CPSR_I; s->env->cp15.c1_sys = 0; s->env->cp15.c1_coproc = 0; - s->env->cp15.c2_base = 0; + s->env->cp15.c2_base0 = 0; s->env->cp15.c3 = 0; s->pm_regs[PSSR >> 2] |= 0x8; /* Set STS */ s->pm_regs[RCSR >> 2] |= 0x8; /* Set GPR */ @@ -2031,7 +2031,8 @@ struct pxa2xx_state_s *pxa270_init(unsigned int sdram_size, fprintf(stderr, "Unable to find CPU definition\n"); exit(1); } - register_savevm("cpu", 0, 0, cpu_save, cpu_load, s->env); + register_savevm("cpu", 0, ARM_CPU_SAVE_VERSION, cpu_save, cpu_load, + s->env); /* SDRAM & Internal Memory Storage */ cpu_register_physical_memory(PXA2XX_SDRAM_BASE, @@ -2145,7 +2146,8 @@ struct pxa2xx_state_s *pxa255_init(unsigned int sdram_size, fprintf(stderr, "Unable to find CPU definition\n"); exit(1); } - register_savevm("cpu", 0, 0, cpu_save, cpu_load, s->env); + register_savevm("cpu", 0, ARM_CPU_SAVE_VERSION, cpu_save, cpu_load, + s->env); /* SDRAM & Internal Memory Storage */ cpu_register_physical_memory(PXA2XX_SDRAM_BASE, sdram_size, diff --git a/hw/realview.c b/hw/realview.c index f97d6e625a..e02deeed74 100644 --- a/hw/realview.c +++ b/hw/realview.c @@ -25,13 +25,32 @@ static void realview_init(int ram_size, int vga_ram_size, NICInfo *nd; int n; int done_smc = 0; + qemu_irq cpu_irq[4]; + int ncpu; if (!cpu_model) cpu_model = "arm926"; - env = cpu_init(cpu_model); - if (!env) { - fprintf(stderr, "Unable to find CPU definition\n"); - exit(1); + /* FIXME: obey smp_cpus. */ + if (strcmp(cpu_model, "arm11mpcore") == 0) { + ncpu = 4; + } else { + ncpu = 1; + } + + for (n = 0; n < ncpu; n++) { + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + pic = arm_pic_init_cpu(env); + cpu_irq[n] = pic[ARM_PIC_CPU_IRQ]; + if (n > 0) { + /* Set entry point for secondary CPUs. This assumes we're using + the init code from arm_boot.c. Real hardware resets all CPUs + the same. */ + env->regs[15] = 0x80000000; + } } /* ??? RAM shoud repeat to fill physical memory space. */ @@ -39,18 +58,23 @@ static void realview_init(int ram_size, int vga_ram_size, cpu_register_physical_memory(0, ram_size, IO_MEM_RAM); arm_sysctl_init(0x10000000, 0xc1400400); - pic = arm_pic_init_cpu(env); - /* ??? The documentation says GIC1 is nFIQ and either GIC2 or GIC3 - is nIRQ (there are inconsistencies). However Linux 2.6.17 expects - GIC1 to be nIRQ and ignores all the others, so do that for now. */ - pic = arm_gic_init(0x10040000, pic[ARM_PIC_CPU_IRQ]); + + if (ncpu == 1) { + /* ??? The documentation says GIC1 is nFIQ and either GIC2 or GIC3 + is nIRQ (there are inconsistencies). However Linux 2.6.17 expects + GIC1 to be nIRQ and ignores all the others, so do that for now. */ + pic = realview_gic_init(0x10040000, cpu_irq[0]); + } else { + pic = mpcore_irq_init(cpu_irq); + } + pl050_init(0x10006000, pic[20], 0); pl050_init(0x10007000, pic[21], 1); - pl011_init(0x10009000, pic[12], serial_hds[0]); - pl011_init(0x1000a000, pic[13], serial_hds[1]); - pl011_init(0x1000b000, pic[14], serial_hds[2]); - pl011_init(0x1000c000, pic[15], serial_hds[3]); + pl011_init(0x10009000, pic[12], serial_hds[0], PL011_ARM); + pl011_init(0x1000a000, pic[13], serial_hds[1], PL011_ARM); + pl011_init(0x1000b000, pic[14], serial_hds[2], PL011_ARM); + pl011_init(0x1000c000, pic[15], serial_hds[3], PL011_ARM); /* DMA controller is optional, apparently. */ pl080_init(0x10030000, pic[24], 2); @@ -114,10 +138,10 @@ static void realview_init(int ram_size, int vga_ram_size, /* 0x10019000 PCI controller config. */ /* 0x10020000 CLCD. */ /* 0x10030000 DMA Controller. */ - /* 0x10040000 GIC1 (FIQ1). */ - /* 0x10050000 GIC2 (IRQ1). */ - /* 0x10060000 GIC3 (FIQ2). */ - /* 0x10070000 GIC4 (IRQ2). */ + /* 0x10040000 GIC1. */ + /* 0x10050000 GIC2. */ + /* 0x10060000 GIC3. */ + /* 0x10070000 GIC4. */ /* 0x10080000 SMC. */ /* 0x40000000 NOR flash. */ /* 0x44000000 DoC flash. */ @@ -137,8 +161,14 @@ static void realview_init(int ram_size, int vga_ram_size, /* 0x68000000 PCI mem 1. */ /* 0x6c000000 PCI mem 2. */ - arm_load_kernel(env, ram_size, kernel_filename, kernel_cmdline, + arm_load_kernel(first_cpu, ram_size, kernel_filename, kernel_cmdline, initrd_filename, 0x33b, 0x0); + + /* ??? Hack to map an additional page of ram for the secondary CPU + startup code. I guess this works on real hardware because the + BootROM happens to be in ROM/flash or in memory that isn't clobbered + until after Linux boots the secondary CPUs. */ + cpu_register_physical_memory(0x80000000, 0x1000, IO_MEM_RAM + ram_size); } QEMUMachine realview_machine = { diff --git a/hw/realview_gic.c b/hw/realview_gic.c new file mode 100644 index 0000000000..cbc961491c --- /dev/null +++ b/hw/realview_gic.c @@ -0,0 +1,64 @@ +/* + * ARM RealView Emulation Baseboard Interrupt Controller + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "vl.h" +#include "arm_pic.h" + +#define GIC_NIRQ 96 +#define NCPU 1 + +/* Only a single "CPU" interface is present. */ +static inline int +gic_get_current_cpu(void) +{ + return 0; +} + +#include "arm_gic.c" + +static uint32_t realview_gic_cpu_read(void *opaque, target_phys_addr_t offset) +{ + gic_state *s = (gic_state *)opaque; + offset -= s->base; + return gic_cpu_read(s, gic_get_current_cpu(), offset); +} + +static void realview_gic_cpu_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + gic_state *s = (gic_state *)opaque; + offset -= s->base; + gic_cpu_write(s, gic_get_current_cpu(), offset, value); +} + +static CPUReadMemoryFunc *realview_gic_cpu_readfn[] = { + realview_gic_cpu_read, + realview_gic_cpu_read, + realview_gic_cpu_read +}; + +static CPUWriteMemoryFunc *realview_gic_cpu_writefn[] = { + realview_gic_cpu_write, + realview_gic_cpu_write, + realview_gic_cpu_write +}; + +qemu_irq *realview_gic_init(uint32_t base, qemu_irq parent_irq) +{ + gic_state *s; + int iomemtype; + + s = gic_init(base, &parent_irq); + if (!s) + return NULL; + iomemtype = cpu_register_io_memory(0, realview_gic_cpu_readfn, + realview_gic_cpu_writefn, s); + cpu_register_physical_memory(base, 0x00001000, iomemtype); + return s->in; +} diff --git a/hw/ssd0303.c b/hw/ssd0303.c new file mode 100644 index 0000000000..138cfc7fa7 --- /dev/null +++ b/hw/ssd0303.c @@ -0,0 +1,273 @@ +/* + * SSD0303 OLED controller with OSRAM Pictiva 96x16 display. + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +/* The controller can support a variety of different displays, but we only + implement one. Most of the commends relating to brightness and geometry + setup are ignored. */ +#include "vl.h" + +//#define DEBUG_SSD0303 1 + +#ifdef DEBUG_SSD0303 +#define DPRINTF(fmt, args...) \ +do { printf("ssd0303: " fmt , ##args); } while (0) +#define BADF(fmt, args...) \ +do { fprintf(stderr, "ssd0303: error: " fmt , ##args); exit(1);} while (0) +#else +#define DPRINTF(fmt, args...) do {} while(0) +#define BADF(fmt, args...) \ +do { fprintf(stderr, "ssd0303: error: " fmt , ##args);} while (0) +#endif + +/* Scaling factor for pixels. */ +#define MAGNIFY 4 + +enum ssd0303_mode +{ + SSD0303_IDLE, + SSD0303_DATA, + SSD0303_CMD +}; + +enum ssd0303_cmd { + SSD0303_CMD_NONE, + SSD0303_CMD_SKIP1 +}; + +typedef struct { + i2c_slave i2c; + DisplayState *ds; + int row; + int col; + int start_line; + int mirror; + int flash; + int enabled; + int inverse; + int redraw; + enum ssd0303_mode mode; + enum ssd0303_cmd cmd_state; + uint8_t framebuffer[132*8]; +} ssd0303_state; + +static int ssd0303_recv(i2c_slave *i2c) +{ + BADF("Reads not implemented\n"); + return -1; +} + +static int ssd0303_send(i2c_slave *i2c, uint8_t data) +{ + ssd0303_state *s = (ssd0303_state *)i2c; + enum ssd0303_cmd old_cmd_state; + switch (s->mode) { + case SSD0303_IDLE: + DPRINTF("byte 0x%02x\n", data); + if (data == 0x80) + s->mode = SSD0303_CMD; + else if (data == 0x40) + s->mode = SSD0303_DATA; + else + BADF("Unexpected byte 0x%x\n", data); + break; + case SSD0303_DATA: + DPRINTF("data 0x%02x\n", data); + if (s->col < 132) { + s->framebuffer[s->col + s->row * 132] = data; + s->col++; + s->redraw = 1; + } + break; + case SSD0303_CMD: + old_cmd_state = s->cmd_state; + s->cmd_state = SSD0303_CMD_NONE; + switch (old_cmd_state) { + case SSD0303_CMD_NONE: + DPRINTF("cmd 0x%02x\n", data); + s->mode = SSD0303_IDLE; + switch (data) { + case 0x00 ... 0x0f: /* Set lower colum address. */ + s->col = (s->col & 0xf0) | (data & 0xf); + break; + case 0x10 ... 0x20: /* Set higher column address. */ + s->col = (s->col & 0x0f) | ((data & 0xf) << 4); + break; + case 0x40 ... 0x7f: /* Set start line. */ + s->start_line = 0; + break; + case 0x81: /* Set contrast (Ignored). */ + s->cmd_state = SSD0303_CMD_SKIP1; + break; + case 0xa0: /* Mirror off. */ + s->mirror = 0; + break; + case 0xa1: /* Mirror off. */ + s->mirror = 1; + break; + case 0xa4: /* Entire display off. */ + s->flash = 0; + break; + case 0xa5: /* Entire display on. */ + s->flash = 1; + break; + case 0xa6: /* Inverse off. */ + s->inverse = 0; + break; + case 0xa7: /* Inverse on. */ + s->inverse = 1; + break; + case 0xa8: /* Set multipled ratio (Ignored). */ + s->cmd_state = SSD0303_CMD_SKIP1; + break; + case 0xad: /* DC-DC power control. */ + s->cmd_state = SSD0303_CMD_SKIP1; + break; + case 0xae: /* Display off. */ + s->enabled = 0; + break; + case 0xaf: /* Display on. */ + s->enabled = 1; + break; + case 0xb0 ... 0xbf: /* Set Page address. */ + s->row = data & 7; + break; + case 0xc0 ... 0xc8: /* Set COM output direction (Ignored). */ + break; + case 0xd3: /* Set display offset (Ignored). */ + s->cmd_state = SSD0303_CMD_SKIP1; + break; + case 0xd5: /* Set display clock (Ignored). */ + s->cmd_state = SSD0303_CMD_SKIP1; + break; + case 0xd8: /* Set color and power mode (Ignored). */ + s->cmd_state = SSD0303_CMD_SKIP1; + break; + case 0xd9: /* Set pre-charge period (Ignored). */ + s->cmd_state = SSD0303_CMD_SKIP1; + break; + case 0xda: /* Set COM pin configuration (Ignored). */ + s->cmd_state = SSD0303_CMD_SKIP1; + break; + case 0xdb: /* Set VCOM dselect level (Ignored). */ + s->cmd_state = SSD0303_CMD_SKIP1; + break; + case 0xe3: /* no-op. */ + break; + default: + BADF("Unknown command: 0x%x\n", data); + } + break; + case SSD0303_CMD_SKIP1: + DPRINTF("skip 0x%02x\n", data); + break; + } + break; + } + return 0; +} + +static void ssd0303_event(i2c_slave *i2c, enum i2c_event event) +{ + ssd0303_state *s = (ssd0303_state *)i2c; + switch (event) { + case I2C_FINISH: + s->mode = SSD0303_IDLE; + break; + case I2C_START_RECV: + case I2C_START_SEND: + case I2C_NACK: + /* Nothing to do. */ + break; + } +} + +static void ssd0303_update_display(void *opaque) +{ + ssd0303_state *s = (ssd0303_state *)opaque; + uint8_t *dest; + uint8_t *src; + int x; + int y; + int line; + char *colors[2]; + char colortab[MAGNIFY * 8]; + int dest_width; + uint8_t mask; + + if (s->redraw) { + switch (s->ds->depth) { + case 0: + return; + case 15: + dest_width = 2; + break; + case 16: + dest_width = 2; + break; + case 24: + dest_width = 3; + break; + case 32: + dest_width = 4; + break; + default: + BADF("Bad color depth\n"); + return; + } + dest_width *= MAGNIFY; + memset(colortab, 0xff, dest_width); + memset(colortab + dest_width, 0, dest_width); + if (s->flash) { + colors[0] = colortab; + colors[1] = colortab; + } else if (s->inverse) { + colors[0] = colortab; + colors[1] = colortab + dest_width; + } else { + colors[0] = colortab + dest_width; + colors[1] = colortab; + } + dest = s->ds->data; + for (y = 0; y < 16; y++) { + line = (y + s->start_line) & 63; + src = s->framebuffer + 132 * (line >> 3) + 36; + mask = 1 << (line & 7); + for (x = 0; x < 96; x++) { + memcpy(dest, colors[(*src & mask) != 0], dest_width); + dest += dest_width; + src++; + } + for (x = 1; x < MAGNIFY; x++) { + memcpy(dest, dest - dest_width * 96, dest_width * 96); + dest += dest_width * 96; + } + } + } + dpy_update(s->ds, 0, 0, 96 * MAGNIFY, 16 * MAGNIFY); +} + +static void ssd0303_invalidate_display(void * opaque) +{ + ssd0303_state *s = (ssd0303_state *)opaque; + s->redraw = 1; +} + +void ssd0303_init(DisplayState *ds, i2c_bus *bus, int address) +{ + ssd0303_state *s; + + s = (ssd0303_state *)i2c_slave_init(bus, address, sizeof(ssd0303_state)); + s->ds = ds; + s->i2c.event = ssd0303_event; + s->i2c.recv = ssd0303_recv; + s->i2c.send = ssd0303_send; + graphic_console_init(ds, ssd0303_update_display, ssd0303_invalidate_display, + NULL, s); + dpy_resize(s->ds, 96 * MAGNIFY, 16 * MAGNIFY); +} diff --git a/hw/ssd0323.c b/hw/ssd0323.c new file mode 100644 index 0000000000..67361bce2e --- /dev/null +++ b/hw/ssd0323.c @@ -0,0 +1,267 @@ +/* + * SSD0323 OLED controller with OSRAM Pictiva 128x64 display. + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +/* The controller can support a variety of different displays, but we only + implement one. Most of the commends relating to brightness and geometry + setup are ignored. */ +#include "vl.h" + +//#define DEBUG_SSD0323 1 + +#ifdef DEBUG_SSD0323 +#define DPRINTF(fmt, args...) \ +do { printf("ssd0323: " fmt , ##args); } while (0) +#define BADF(fmt, args...) \ +do { fprintf(stderr, "ssd0323: error: " fmt , ##args); exit(1);} while (0) +#else +#define DPRINTF(fmt, args...) do {} while(0) +#define BADF(fmt, args...) \ +do { fprintf(stderr, "ssd0323: error: " fmt , ##args);} while (0) +#endif + +/* Scaling factor for pixels. */ +#define MAGNIFY 4 + +enum ssd0323_mode +{ + SSD0323_CMD, + SSD0323_DATA +}; + +typedef struct { + DisplayState *ds; + + int cmd_len; + int cmd; + int cmd_data[8]; + int row; + int row_start; + int row_end; + int col; + int col_start; + int col_end; + int redraw; + enum ssd0323_mode mode; + uint8_t framebuffer[128 * 80 / 2]; +} ssd0323_state; + +int ssd0323_xfer_ssi(void *opaque, int data) +{ + ssd0323_state *s = (ssd0323_state *)opaque; + switch (s->mode) { + case SSD0323_DATA: + DPRINTF("data 0x%02x\n", data); + s->framebuffer[s->col + s->row * 64] = data; + s->col++; + if (s->col > s->col_end) { + s->row++; + s->col = s->col_start; + } + if (s->row > s->row_end) { + s->row = s->row_start; + } + s->redraw = 1; + break; + case SSD0323_CMD: + DPRINTF("cmd 0x%02x\n", data); + if (s->cmd_len == 0) { + s->cmd = data; + } else { + s->cmd_data[s->cmd_len - 1] = data; + } + s->cmd_len++; + switch (s->cmd) { +#define DATA(x) if (s->cmd_len <= (x)) return 0 + case 0x15: /* Set column. */ + DATA(2); + s->col_start = s->cmd_data[0] % 64; + s->col_end = s->cmd_data[1] % 64; + break; + case 0x75: /* Set row. */ + DATA(2); + s->row_start = s->cmd_data[0] % 80; + s->row_end = s->cmd_data[1] % 80; + break; + case 0x81: /* Set contrast */ + DATA(1); + break; + case 0x84: case 0x85: case 0x86: /* Max current. */ + DATA(0); + break; + case 0xa0: /* Set remapping. */ + /* FIXME: Implement this. */ + DATA(1); + break; + case 0xa1: /* Set display start line. */ + case 0xa2: /* Set display offset. */ + /* FIXME: Implement these. */ + DATA(1); + break; + case 0xa4: /* Normal mode. */ + case 0xa5: /* All on. */ + case 0xa6: /* All off. */ + case 0xa7: /* Inverse. */ + /* FIXME: Implement these. */ + DATA(0); + break; + case 0xa8: /* Set multiplex ratio. */ + case 0xad: /* Set DC-DC converter. */ + DATA(1); + /* Ignored. Don't care. */ + break; + case 0xae: /* Display off. */ + case 0xaf: /* Display on. */ + DATA(0); + /* TODO: Implement power control. */ + break; + case 0xb1: /* Set phase length. */ + case 0xb2: /* Set row period. */ + case 0xb3: /* Set clock rate. */ + case 0xbc: /* Set precharge. */ + case 0xbe: /* Set VCOMH. */ + case 0xbf: /* Set segment low. */ + DATA(1); + /* Ignored. Don't care. */ + break; + case 0xb8: /* Set grey scale table. */ + /* FIXME: Implement this. */ + DATA(8); + break; + case 0xe3: /* NOP. */ + DATA(0); + break; + default: + BADF("Unknown command: 0x%x\n", data); + } + s->cmd_len = 0; + return 0; + } + return 0; +} + +static void ssd0323_update_display(void *opaque) +{ + ssd0323_state *s = (ssd0323_state *)opaque; + uint8_t *dest; + uint8_t *src; + int x; + int y; + int i; + int line; + char *colors[16]; + char colortab[MAGNIFY * 64]; + char *p; + int dest_width; + + if (s->redraw) { + switch (s->ds->depth) { + case 0: + return; + case 15: + dest_width = 2; + break; + case 16: + dest_width = 2; + break; + case 24: + dest_width = 3; + break; + case 32: + dest_width = 4; + break; + default: + BADF("Bad color depth\n"); + return; + } + p = colortab; + for (i = 0; i < 16; i++) { + int n; + colors[i] = p; + switch (s->ds->depth) { + case 15: + n = i * 2 + (i >> 3); + p[0] = n | (n << 5); + p[1] = (n << 2) | (n >> 3); + break; + case 16: + n = i * 2 + (i >> 3); + p[0] = n | (n << 6) | ((n << 1) & 0x20); + p[1] = (n << 3) | (n >> 2); + break; + case 24: + case 32: + n = (i << 4) | i; + p[0] = p[1] = p[2] = n; + break; + default: + BADF("Bad color depth\n"); + return; + } + p += dest_width; + } + dest = s->ds->data; + for (y = 0; y < 64; y++) { + line = y; + src = s->framebuffer + 64 * line; + for (x = 0; x < 64; x++) { + int val; + val = *src >> 4; + for (i = 0; i < MAGNIFY; i++) { + memcpy(dest, colors[val], dest_width); + dest += dest_width; + } + val = *src & 0xf; + for (i = 0; i < MAGNIFY; i++) { + memcpy(dest, colors[val], dest_width); + dest += dest_width; + } + src++; + } + for (i = 1; i < MAGNIFY; i++) { + memcpy(dest, dest - dest_width * MAGNIFY * 128, + dest_width * 128 * MAGNIFY); + dest += dest_width * 128 * MAGNIFY; + } + } + } + dpy_update(s->ds, 0, 0, 128 * MAGNIFY, 64 * MAGNIFY); +} + +static void ssd0323_invalidate_display(void * opaque) +{ + ssd0323_state *s = (ssd0323_state *)opaque; + s->redraw = 1; +} + +/* Command/data input. */ +static void ssd0323_cd(void *opaque, int n, int level) +{ + ssd0323_state *s = (ssd0323_state *)opaque; + DPRINTF("%s mode\n", level ? "Data" : "Command"); + s->mode = level ? SSD0323_DATA : SSD0323_CMD; +} + +void *ssd0323_init(DisplayState *ds, qemu_irq *cmd_p) +{ + ssd0323_state *s; + qemu_irq *cmd; + + s = (ssd0323_state *)qemu_mallocz(sizeof(ssd0323_state)); + s->ds = ds; + graphic_console_init(ds, ssd0323_update_display, ssd0323_invalidate_display, + NULL, s); + dpy_resize(s->ds, 128 * MAGNIFY, 64 * MAGNIFY); + s->col_end = 63; + s->row_end = 79; + + cmd = qemu_allocate_irqs(ssd0323_cd, s, 1); + *cmd_p = *cmd; + + return s; +} diff --git a/hw/stellaris.c b/hw/stellaris.c new file mode 100644 index 0000000000..62f2c03445 --- /dev/null +++ b/hw/stellaris.c @@ -0,0 +1,1101 @@ +/* + * Luminary Micro Stellaris preipherals + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "vl.h" +#include "arm_pic.h" + +typedef const struct { + const char *name; + uint32_t did0; + uint32_t did1; + uint32_t dc0; + uint32_t dc1; + uint32_t dc2; + uint32_t dc3; + uint32_t dc4; + enum {OLED_I2C, OLED_SSI} oled; +} stellaris_board_info; + +/* General purpose timer module. */ + +/* Multiplication factor to convert from GPTM timer ticks to qemu timer + ticks. */ +static int stellaris_clock_scale; + +typedef struct gptm_state { + uint32_t config; + uint32_t mode[2]; + uint32_t control; + uint32_t state; + uint32_t mask; + uint32_t load[2]; + uint32_t match[2]; + uint32_t prescale[2]; + uint32_t match_prescale[2]; + uint32_t rtc; + int64_t tick[2]; + struct gptm_state *opaque[2]; + uint32_t base; + QEMUTimer *timer[2]; + /* The timers have an alternate output used to trigger the ADC. */ + qemu_irq trigger; + qemu_irq irq; +} gptm_state; + +static void gptm_update_irq(gptm_state *s) +{ + int level; + level = (s->state & s->mask) != 0; + qemu_set_irq(s->irq, level); +} + +static void gptm_stop(gptm_state *s, int n) +{ + qemu_del_timer(s->timer[n]); +} + +static void gptm_reload(gptm_state *s, int n, int reset) +{ + int64_t tick; + if (reset) + tick = qemu_get_clock(vm_clock); + else + tick = s->tick[n]; + + if (s->config == 0) { + /* 32-bit CountDown. */ + uint32_t count; + count = s->load[0] | (s->load[1] << 16); + tick += (int64_t)count * stellaris_clock_scale; + } else if (s->config == 1) { + /* 32-bit RTC. 1Hz tick. */ + tick += ticks_per_sec; + } else if (s->mode[n] == 0xa) { + /* PWM mode. Not implemented. */ + } else { + cpu_abort(cpu_single_env, "TODO: 16-bit timer mode 0x%x\n", + s->mode[n]); + } + s->tick[n] = tick; + qemu_mod_timer(s->timer[n], tick); +} + +static void gptm_tick(void *opaque) +{ + gptm_state **p = (gptm_state **)opaque; + gptm_state *s; + int n; + + s = *p; + n = p - s->opaque; + if (s->config == 0) { + s->state |= 1; + if ((s->control & 0x20)) { + /* Output trigger. */ + qemu_irq_raise(s->trigger); + qemu_irq_lower(s->trigger); + } + if (s->mode[0] & 1) { + /* One-shot. */ + s->control &= ~1; + } else { + /* Periodic. */ + gptm_reload(s, 0, 0); + } + } else if (s->config == 1) { + /* RTC. */ + uint32_t match; + s->rtc++; + match = s->match[0] | (s->match[1] << 16); + if (s->rtc > match) + s->rtc = 0; + if (s->rtc == 0) { + s->state |= 8; + } + gptm_reload(s, 0, 0); + } else if (s->mode[n] == 0xa) { + /* PWM mode. Not implemented. */ + } else { + cpu_abort(cpu_single_env, "TODO: 16-bit timer mode 0x%x\n", + s->mode[n]); + } + gptm_update_irq(s); +} + +static uint32_t gptm_read(void *opaque, target_phys_addr_t offset) +{ + gptm_state *s = (gptm_state *)opaque; + + offset -= s->base; + switch (offset) { + case 0x00: /* CFG */ + return s->config; + case 0x04: /* TAMR */ + return s->mode[0]; + case 0x08: /* TBMR */ + return s->mode[1]; + case 0x0c: /* CTL */ + return s->control; + case 0x18: /* IMR */ + return s->mask; + case 0x1c: /* RIS */ + return s->state; + case 0x20: /* MIS */ + return s->state & s->mask; + case 0x24: /* CR */ + return 0; + case 0x28: /* TAILR */ + return s->load[0] | ((s->config < 4) ? (s->load[1] << 16) : 0); + case 0x2c: /* TBILR */ + return s->load[1]; + case 0x30: /* TAMARCHR */ + return s->match[0] | ((s->config < 4) ? (s->match[1] << 16) : 0); + case 0x34: /* TBMATCHR */ + return s->match[1]; + case 0x38: /* TAPR */ + return s->prescale[0]; + case 0x3c: /* TBPR */ + return s->prescale[1]; + case 0x40: /* TAPMR */ + return s->match_prescale[0]; + case 0x44: /* TBPMR */ + return s->match_prescale[1]; + case 0x48: /* TAR */ + if (s->control == 1) + return s->rtc; + case 0x4c: /* TBR */ + cpu_abort(cpu_single_env, "TODO: Timer value read\n"); + default: + cpu_abort(cpu_single_env, "gptm_read: Bad offset 0x%x\n", (int)offset); + return 0; + } +} + +static void gptm_write(void *opaque, target_phys_addr_t offset, uint32_t value) +{ + gptm_state *s = (gptm_state *)opaque; + uint32_t oldval; + + offset -= s->base; + /* The timers should be disabled before changing the configuration. + We take advantage of this and defer everything until the timer + is enabled. */ + switch (offset) { + case 0x00: /* CFG */ + s->config = value; + break; + case 0x04: /* TAMR */ + s->mode[0] = value; + break; + case 0x08: /* TBMR */ + s->mode[1] = value; + break; + case 0x0c: /* CTL */ + oldval = s->control; + s->control = value; + /* TODO: Implement pause. */ + if ((oldval ^ value) & 1) { + if (value & 1) { + gptm_reload(s, 0, 1); + } else { + gptm_stop(s, 0); + } + } + if (((oldval ^ value) & 0x100) && s->config >= 4) { + if (value & 0x100) { + gptm_reload(s, 1, 1); + } else { + gptm_stop(s, 1); + } + } + break; + case 0x18: /* IMR */ + s->mask = value & 0x77; + gptm_update_irq(s); + break; + case 0x24: /* CR */ + s->state &= ~value; + break; + case 0x28: /* TAILR */ + s->load[0] = value & 0xffff; + if (s->config < 4) { + s->load[1] = value >> 16; + } + break; + case 0x2c: /* TBILR */ + s->load[1] = value & 0xffff; + break; + case 0x30: /* TAMARCHR */ + s->match[0] = value & 0xffff; + if (s->config < 4) { + s->match[1] = value >> 16; + } + break; + case 0x34: /* TBMATCHR */ + s->match[1] = value >> 16; + break; + case 0x38: /* TAPR */ + s->prescale[0] = value; + break; + case 0x3c: /* TBPR */ + s->prescale[1] = value; + break; + case 0x40: /* TAPMR */ + s->match_prescale[0] = value; + break; + case 0x44: /* TBPMR */ + s->match_prescale[0] = value; + break; + default: + cpu_abort(cpu_single_env, "gptm_write: Bad offset 0x%x\n", (int)offset); + } + gptm_update_irq(s); +} + +static CPUReadMemoryFunc *gptm_readfn[] = { + gptm_read, + gptm_read, + gptm_read +}; + +static CPUWriteMemoryFunc *gptm_writefn[] = { + gptm_write, + gptm_write, + gptm_write +}; + +static void stellaris_gptm_init(uint32_t base, qemu_irq irq, qemu_irq trigger) +{ + int iomemtype; + gptm_state *s; + + s = (gptm_state *)qemu_mallocz(sizeof(gptm_state)); + s->base = base; + s->irq = irq; + s->trigger = trigger; + s->opaque[0] = s->opaque[1] = s; + + iomemtype = cpu_register_io_memory(0, gptm_readfn, + gptm_writefn, s); + cpu_register_physical_memory(base, 0x00001000, iomemtype); + s->timer[0] = qemu_new_timer(vm_clock, gptm_tick, &s->opaque[0]); + s->timer[1] = qemu_new_timer(vm_clock, gptm_tick, &s->opaque[1]); + /* ??? Save/restore. */ +} + + +/* System controller. */ + +typedef struct { + uint32_t base; + uint32_t pborctl; + uint32_t ldopctl; + uint32_t int_status; + uint32_t int_mask; + uint32_t resc; + uint32_t rcc; + uint32_t rcgc[3]; + uint32_t scgc[3]; + uint32_t dcgc[3]; + uint32_t clkvclr; + uint32_t ldoarst; + qemu_irq irq; + stellaris_board_info *board; +} ssys_state; + +static void ssys_update(ssys_state *s) +{ + qemu_set_irq(s->irq, (s->int_status & s->int_mask) != 0); +} + +static uint32_t pllcfg_sandstorm[16] = { + 0x31c0, /* 1 Mhz */ + 0x1ae0, /* 1.8432 Mhz */ + 0x18c0, /* 2 Mhz */ + 0xd573, /* 2.4576 Mhz */ + 0x37a6, /* 3.57954 Mhz */ + 0x1ae2, /* 3.6864 Mhz */ + 0x0c40, /* 4 Mhz */ + 0x98bc, /* 4.906 Mhz */ + 0x935b, /* 4.9152 Mhz */ + 0x09c0, /* 5 Mhz */ + 0x4dee, /* 5.12 Mhz */ + 0x0c41, /* 6 Mhz */ + 0x75db, /* 6.144 Mhz */ + 0x1ae6, /* 7.3728 Mhz */ + 0x0600, /* 8 Mhz */ + 0x585b /* 8.192 Mhz */ +}; + +static uint32_t pllcfg_fury[16] = { + 0x3200, /* 1 Mhz */ + 0x1b20, /* 1.8432 Mhz */ + 0x1900, /* 2 Mhz */ + 0xf42b, /* 2.4576 Mhz */ + 0x37e3, /* 3.57954 Mhz */ + 0x1b21, /* 3.6864 Mhz */ + 0x0c80, /* 4 Mhz */ + 0x98ee, /* 4.906 Mhz */ + 0xd5b4, /* 4.9152 Mhz */ + 0x0a00, /* 5 Mhz */ + 0x4e27, /* 5.12 Mhz */ + 0x1902, /* 6 Mhz */ + 0xec1c, /* 6.144 Mhz */ + 0x1b23, /* 7.3728 Mhz */ + 0x0640, /* 8 Mhz */ + 0xb11c /* 8.192 Mhz */ +}; + +static uint32_t ssys_read(void *opaque, target_phys_addr_t offset) +{ + ssys_state *s = (ssys_state *)opaque; + + offset -= s->base; + switch (offset) { + case 0x000: /* DID0 */ + return s->board->did0; + case 0x004: /* DID1 */ + return s->board->did1; + case 0x008: /* DC0 */ + return s->board->dc0; + case 0x010: /* DC1 */ + return s->board->dc1; + case 0x014: /* DC2 */ + return s->board->dc2; + case 0x018: /* DC3 */ + return s->board->dc3; + case 0x01c: /* DC4 */ + return s->board->dc4; + case 0x030: /* PBORCTL */ + return s->pborctl; + case 0x034: /* LDOPCTL */ + return s->ldopctl; + case 0x040: /* SRCR0 */ + return 0; + case 0x044: /* SRCR1 */ + return 0; + case 0x048: /* SRCR2 */ + return 0; + case 0x050: /* RIS */ + return s->int_status; + case 0x054: /* IMC */ + return s->int_mask; + case 0x058: /* MISC */ + return s->int_status & s->int_mask; + case 0x05c: /* RESC */ + return s->resc; + case 0x060: /* RCC */ + return s->rcc; + case 0x064: /* PLLCFG */ + { + int xtal; + xtal = (s->rcc >> 6) & 0xf; + if (s->board->did0 & (1 << 16)) { + return pllcfg_fury[xtal]; + } else { + return pllcfg_sandstorm[xtal]; + } + } + case 0x100: /* RCGC0 */ + return s->rcgc[0]; + case 0x104: /* RCGC1 */ + return s->rcgc[1]; + case 0x108: /* RCGC2 */ + return s->rcgc[2]; + case 0x110: /* SCGC0 */ + return s->scgc[0]; + case 0x114: /* SCGC1 */ + return s->scgc[1]; + case 0x118: /* SCGC2 */ + return s->scgc[2]; + case 0x120: /* DCGC0 */ + return s->dcgc[0]; + case 0x124: /* DCGC1 */ + return s->dcgc[1]; + case 0x128: /* DCGC2 */ + return s->dcgc[2]; + case 0x150: /* CLKVCLR */ + return s->clkvclr; + case 0x160: /* LDOARST */ + return s->ldoarst; + default: + cpu_abort(cpu_single_env, "gptm_read: Bad offset 0x%x\n", (int)offset); + return 0; + } +} + +static void ssys_write(void *opaque, target_phys_addr_t offset, uint32_t value) +{ + ssys_state *s = (ssys_state *)opaque; + + offset -= s->base; + switch (offset) { + case 0x030: /* PBORCTL */ + s->pborctl = value & 0xffff; + break; + case 0x034: /* LDOPCTL */ + s->ldopctl = value & 0x1f; + break; + case 0x040: /* SRCR0 */ + case 0x044: /* SRCR1 */ + case 0x048: /* SRCR2 */ + fprintf(stderr, "Peripheral reset not implemented\n"); + break; + case 0x054: /* IMC */ + s->int_mask = value & 0x7f; + break; + case 0x058: /* MISC */ + s->int_status &= ~value; + break; + case 0x05c: /* RESC */ + s->resc = value & 0x3f; + break; + case 0x060: /* RCC */ + if ((s->rcc & (1 << 13)) != 0 && (value & (1 << 13)) == 0) { + /* PLL enable. */ + s->int_status |= (1 << 6); + } + s->rcc = value; + stellaris_clock_scale = 5 * (((s->rcc >> 23) & 0xf) + 1); + break; + case 0x100: /* RCGC0 */ + s->rcgc[0] = value; + break; + case 0x104: /* RCGC1 */ + s->rcgc[1] = value; + break; + case 0x108: /* RCGC2 */ + s->rcgc[2] = value; + break; + case 0x110: /* SCGC0 */ + s->scgc[0] = value; + break; + case 0x114: /* SCGC1 */ + s->scgc[1] = value; + break; + case 0x118: /* SCGC2 */ + s->scgc[2] = value; + break; + case 0x120: /* DCGC0 */ + s->dcgc[0] = value; + break; + case 0x124: /* DCGC1 */ + s->dcgc[1] = value; + break; + case 0x128: /* DCGC2 */ + s->dcgc[2] = value; + break; + case 0x150: /* CLKVCLR */ + s->clkvclr = value; + break; + case 0x160: /* LDOARST */ + s->ldoarst = value; + break; + default: + cpu_abort(cpu_single_env, "gptm_write: Bad offset 0x%x\n", (int)offset); + } + ssys_update(s); +} + +static CPUReadMemoryFunc *ssys_readfn[] = { + ssys_read, + ssys_read, + ssys_read +}; + +static CPUWriteMemoryFunc *ssys_writefn[] = { + ssys_write, + ssys_write, + ssys_write +}; + +void ssys_reset(void *opaque) +{ + ssys_state *s = (ssys_state *)opaque; + + s->pborctl = 0x7ffd; + s->rcc = 0x078e3ac0; + s->rcgc[0] = 1; + s->scgc[0] = 1; + s->dcgc[0] = 1; +} + +static void stellaris_sys_init(uint32_t base, qemu_irq irq, + stellaris_board_info * board) +{ + int iomemtype; + ssys_state *s; + + s = (ssys_state *)qemu_mallocz(sizeof(ssys_state)); + s->base = base; + s->irq = irq; + s->board = board; + + iomemtype = cpu_register_io_memory(0, ssys_readfn, + ssys_writefn, s); + cpu_register_physical_memory(base, 0x00001000, iomemtype); + ssys_reset(s); + /* ??? Save/restore. */ +} + + +/* I2C controller. */ + +typedef struct { + i2c_bus *bus; + qemu_irq irq; + uint32_t base; + uint32_t msa; + uint32_t mcs; + uint32_t mdr; + uint32_t mtpr; + uint32_t mimr; + uint32_t mris; + uint32_t mcr; +} stellaris_i2c_state; + +#define STELLARIS_I2C_MCS_BUSY 0x01 +#define STELLARIS_I2C_MCS_ERROR 0x02 +#define STELLARIS_I2C_MCS_ADRACK 0x04 +#define STELLARIS_I2C_MCS_DATACK 0x08 +#define STELLARIS_I2C_MCS_ARBLST 0x10 +#define STELLARIS_I2C_MCS_IDLE 0x20 +#define STELLARIS_I2C_MCS_BUSBSY 0x40 + +static uint32_t stellaris_i2c_read(void *opaque, target_phys_addr_t offset) +{ + stellaris_i2c_state *s = (stellaris_i2c_state *)opaque; + + offset -= s->base; + switch (offset) { + case 0x00: /* MSA */ + return s->msa; + case 0x04: /* MCS */ + /* We don't emulate timing, so the controller is never busy. */ + return s->mcs | STELLARIS_I2C_MCS_IDLE; + case 0x08: /* MDR */ + return s->mdr; + case 0x0c: /* MTPR */ + return s->mtpr; + case 0x10: /* MIMR */ + return s->mimr; + case 0x14: /* MRIS */ + return s->mris; + case 0x18: /* MMIS */ + return s->mris & s->mimr; + case 0x20: /* MCR */ + return s->mcr; + default: + cpu_abort(cpu_single_env, "strllaris_i2c_read: Bad offset 0x%x\n", + (int)offset); + return 0; + } +} + +static void stellaris_i2c_update(stellaris_i2c_state *s) +{ + int level; + + level = (s->mris & s->mimr) != 0; + qemu_set_irq(s->irq, level); +} + +static void stellaris_i2c_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + stellaris_i2c_state *s = (stellaris_i2c_state *)opaque; + + offset -= s->base; + switch (offset) { + case 0x00: /* MSA */ + s->msa = value & 0xff; + break; + case 0x04: /* MCS */ + if ((s->mcr & 0x10) == 0) { + /* Disabled. Do nothing. */ + break; + } + /* Grab the bus if this is starting a transfer. */ + if ((value & 2) && (s->mcs & STELLARIS_I2C_MCS_BUSBSY) == 0) { + if (i2c_start_transfer(s->bus, s->msa >> 1, s->msa & 1)) { + s->mcs |= STELLARIS_I2C_MCS_ARBLST; + } else { + s->mcs &= ~STELLARIS_I2C_MCS_ARBLST; + s->mcs |= STELLARIS_I2C_MCS_BUSBSY; + } + } + /* If we don't have the bus then indicate an error. */ + if (!i2c_bus_busy(s->bus) + || (s->mcs & STELLARIS_I2C_MCS_BUSBSY) == 0) { + s->mcs |= STELLARIS_I2C_MCS_ERROR; + break; + } + s->mcs &= ~STELLARIS_I2C_MCS_ERROR; + if (value & 1) { + /* Transfer a byte. */ + /* TODO: Handle errors. */ + if (s->msa & 1) { + /* Recv */ + s->mdr = i2c_recv(s->bus) & 0xff; + } else { + /* Send */ + i2c_send(s->bus, s->mdr); + } + /* Raise an interrupt. */ + s->mris |= 1; + } + if (value & 4) { + /* Finish transfer. */ + i2c_end_transfer(s->bus); + s->mcs &= ~STELLARIS_I2C_MCS_BUSBSY; + } + break; + case 0x08: /* MDR */ + s->mdr = value & 0xff; + break; + case 0x0c: /* MTPR */ + s->mtpr = value & 0xff; + break; + case 0x10: /* MIMR */ + s->mimr = 1; + break; + case 0x1c: /* MICR */ + s->mris &= ~value; + break; + case 0x20: /* MCR */ + if (value & 1) + cpu_abort(cpu_single_env, + "stellaris_i2c_write: Loopback not implemented\n"); + if (value & 0x20) + cpu_abort(cpu_single_env, + "stellaris_i2c_write: Slave mode not implemented\n"); + s->mcr = value & 0x31; + break; + default: + cpu_abort(cpu_single_env, "stellaris_i2c_write: Bad offset 0x%x\n", + (int)offset); + } + stellaris_i2c_update(s); +} + +static void stellaris_i2c_reset(stellaris_i2c_state *s) +{ + if (s->mcs & STELLARIS_I2C_MCS_BUSBSY) + i2c_end_transfer(s->bus); + + s->msa = 0; + s->mcs = 0; + s->mdr = 0; + s->mtpr = 1; + s->mimr = 0; + s->mris = 0; + s->mcr = 0; + stellaris_i2c_update(s); +} + +static CPUReadMemoryFunc *stellaris_i2c_readfn[] = { + stellaris_i2c_read, + stellaris_i2c_read, + stellaris_i2c_read +}; + +static CPUWriteMemoryFunc *stellaris_i2c_writefn[] = { + stellaris_i2c_write, + stellaris_i2c_write, + stellaris_i2c_write +}; + +static void stellaris_i2c_init(uint32_t base, qemu_irq irq, i2c_bus *bus) +{ + stellaris_i2c_state *s; + int iomemtype; + + s = (stellaris_i2c_state *)qemu_mallocz(sizeof(stellaris_i2c_state)); + s->base = base; + s->irq = irq; + s->bus = bus; + + iomemtype = cpu_register_io_memory(0, stellaris_i2c_readfn, + stellaris_i2c_writefn, s); + cpu_register_physical_memory(base, 0x00001000, iomemtype); + /* ??? For now we only implement the master interface. */ + stellaris_i2c_reset(s); +} + +/* Analogue to Digital Converter. This is only partially implemented, + enough for applications that use a combined ADC and timer tick. */ + +#define STELLARIS_ADC_EM_CONTROLLER 0 +#define STELLARIS_ADC_EM_COMP 1 +#define STELLARIS_ADC_EM_EXTERNAL 4 +#define STELLARIS_ADC_EM_TIMER 5 +#define STELLARIS_ADC_EM_PWM0 6 +#define STELLARIS_ADC_EM_PWM1 7 +#define STELLARIS_ADC_EM_PWM2 8 + +#define STELLARIS_ADC_FIFO_EMPTY 0x0100 +#define STELLARIS_ADC_FIFO_FULL 0x1000 + +typedef struct +{ + uint32_t base; + uint32_t actss; + uint32_t ris; + uint32_t im; + uint32_t emux; + uint32_t ostat; + uint32_t ustat; + uint32_t sspri; + uint32_t sac; + struct { + uint32_t state; + uint32_t data[16]; + } fifo[4]; + uint32_t ssmux[4]; + uint32_t ssctl[4]; + qemu_irq irq; +} stellaris_adc_state; + +static uint32_t stellaris_adc_fifo_read(stellaris_adc_state *s, int n) +{ + int tail; + + tail = s->fifo[n].state & 0xf; + if (s->fifo[n].state & STELLARIS_ADC_FIFO_EMPTY) { + s->ustat |= 1 << n; + } else { + s->fifo[n].state = (s->fifo[n].state & ~0xf) | ((tail + 1) & 0xf); + s->fifo[n].state &= ~STELLARIS_ADC_FIFO_FULL; + if (tail + 1 == ((s->fifo[n].state >> 4) & 0xf)) + s->fifo[n].state |= STELLARIS_ADC_FIFO_EMPTY; + } + return s->fifo[n].data[tail]; +} + +static void stellaris_adc_fifo_write(stellaris_adc_state *s, int n, + uint32_t value) +{ + int head; + + head = (s->fifo[n].state >> 4) & 0xf; + if (s->fifo[n].state & STELLARIS_ADC_FIFO_FULL) { + s->ostat |= 1 << n; + return; + } + s->fifo[n].data[head] = value; + head = (head + 1) & 0xf; + s->fifo[n].state &= ~STELLARIS_ADC_FIFO_EMPTY; + s->fifo[n].state = (s->fifo[n].state & ~0xf0) | (head << 4); + if ((s->fifo[n].state & 0xf) == head) + s->fifo[n].state |= STELLARIS_ADC_FIFO_FULL; +} + +static void stellaris_adc_update(stellaris_adc_state *s) +{ + int level; + + level = (s->ris & s->im) != 0; + qemu_set_irq(s->irq, level); +} + +static void stellaris_adc_trigger(void *opaque, int irq, int level) +{ + stellaris_adc_state *s = (stellaris_adc_state *)opaque; + /* Some applications use the ADC as a random number source, so introduce + some variation into the signal. */ + static uint32_t noise = 0; + + if ((s->actss & 1) == 0) { + return; + } + + noise = noise * 314159 + 1; + /* ??? actual inputs not implemented. Return an arbitrary value. */ + stellaris_adc_fifo_write(s, 0, 0x200 + ((noise >> 16) & 7)); + s->ris |= 1; + stellaris_adc_update(s); +} + +static void stellaris_adc_reset(stellaris_adc_state *s) +{ + int n; + + for (n = 0; n < 4; n++) { + s->ssmux[n] = 0; + s->ssctl[n] = 0; + s->fifo[n].state = STELLARIS_ADC_FIFO_EMPTY; + } +} + +static uint32_t stellaris_adc_read(void *opaque, target_phys_addr_t offset) +{ + stellaris_adc_state *s = (stellaris_adc_state *)opaque; + + /* TODO: Implement this. */ + offset -= s->base; + if (offset >= 0x40 && offset < 0xc0) { + int n; + n = (offset - 0x40) >> 5; + switch (offset & 0x1f) { + case 0x00: /* SSMUX */ + return s->ssmux[n]; + case 0x04: /* SSCTL */ + return s->ssctl[n]; + case 0x08: /* SSFIFO */ + return stellaris_adc_fifo_read(s, n); + case 0x0c: /* SSFSTAT */ + return s->fifo[n].state; + default: + break; + } + } + switch (offset) { + case 0x00: /* ACTSS */ + return s->actss; + case 0x04: /* RIS */ + return s->ris; + case 0x08: /* IM */ + return s->im; + case 0x0c: /* ISC */ + return s->ris & s->im; + case 0x10: /* OSTAT */ + return s->ostat; + case 0x14: /* EMUX */ + return s->emux; + case 0x18: /* USTAT */ + return s->ustat; + case 0x20: /* SSPRI */ + return s->sspri; + case 0x30: /* SAC */ + return s->sac; + default: + cpu_abort(cpu_single_env, "strllaris_adc_read: Bad offset 0x%x\n", + (int)offset); + return 0; + } +} + +static void stellaris_adc_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + stellaris_adc_state *s = (stellaris_adc_state *)opaque; + + /* TODO: Implement this. */ + offset -= s->base; + if (offset >= 0x40 && offset < 0xc0) { + int n; + n = (offset - 0x40) >> 5; + switch (offset & 0x1f) { + case 0x00: /* SSMUX */ + s->ssmux[n] = value & 0x33333333; + return; + case 0x04: /* SSCTL */ + if (value != 6) { + cpu_abort(cpu_single_env, "ADC: Unimplemented sequence %x\n", + value); + } + s->ssctl[n] = value; + return; + default: + break; + } + } + switch (offset) { + case 0x00: /* ACTSS */ + s->actss = value & 0xf; + if (value & 0xe) { + cpu_abort(cpu_single_env, + "Not implemented: ADC sequencers 1-3\n"); + } + break; + case 0x08: /* IM */ + s->im = value; + break; + case 0x0c: /* ISC */ + s->ris &= ~value; + break; + case 0x10: /* OSTAT */ + s->ostat &= ~value; + break; + case 0x14: /* EMUX */ + s->emux = value; + break; + case 0x18: /* USTAT */ + s->ustat &= ~value; + break; + case 0x20: /* SSPRI */ + s->sspri = value; + break; + case 0x28: /* PSSI */ + cpu_abort(cpu_single_env, "Not implemented: ADC sample initiate\n"); + break; + case 0x30: /* SAC */ + s->sac = value; + break; + default: + cpu_abort(cpu_single_env, "stellaris_adc_write: Bad offset 0x%x\n", + (int)offset); + } + stellaris_adc_update(s); +} + +static CPUReadMemoryFunc *stellaris_adc_readfn[] = { + stellaris_adc_read, + stellaris_adc_read, + stellaris_adc_read +}; + +static CPUWriteMemoryFunc *stellaris_adc_writefn[] = { + stellaris_adc_write, + stellaris_adc_write, + stellaris_adc_write +}; + +static qemu_irq stellaris_adc_init(uint32_t base, qemu_irq irq) +{ + stellaris_adc_state *s; + int iomemtype; + qemu_irq *qi; + + s = (stellaris_adc_state *)qemu_mallocz(sizeof(stellaris_adc_state)); + s->base = base; + s->irq = irq; + + iomemtype = cpu_register_io_memory(0, stellaris_adc_readfn, + stellaris_adc_writefn, s); + cpu_register_physical_memory(base, 0x00001000, iomemtype); + stellaris_adc_reset(s); + qi = qemu_allocate_irqs(stellaris_adc_trigger, s, 1); + return qi[0]; +} + +/* Board init. */ +static stellaris_board_info stellaris_boards[] = { + { "LM3S811EVB", + 0, + 0x0032000e, + 0x001f001f, /* dc0 */ + 0x001132bf, + 0x01071013, + 0x3f0f01ff, + 0x0000001f, + OLED_I2C + }, + { "LM3S6965EVB", + 0x10010002, + 0x1073402e, + 0x00ff007f, /* dc0 */ + 0x001133ff, + 0x030f5317, + 0x0f0f87ff, + 0x5000007f, + OLED_SSI + } +}; + +static void stellaris_init(const char *kernel_filename, const char *cpu_model, + DisplayState *ds, stellaris_board_info *board) +{ + static const int uart_irq[] = {5, 6, 33, 34}; + static const int timer_irq[] = {19, 21, 23, 35}; + static const uint32_t gpio_addr[7] = + { 0x40004000, 0x40005000, 0x40006000, 0x40007000, + 0x40024000, 0x40025000, 0x40026000}; + static const int gpio_irq[7] = {0, 1, 2, 3, 4, 30, 31}; + + qemu_irq *pic; + qemu_irq *gpio_in[5]; + qemu_irq *gpio_out[5]; + qemu_irq adc; + int sram_size; + int flash_size; + i2c_bus *i2c; + int i; + + flash_size = ((board->dc0 & 0xffff) + 1) << 1; + sram_size = (board->dc0 >> 18) + 1; + pic = armv7m_init(flash_size, sram_size, kernel_filename, cpu_model); + + if (board->dc1 & (1 << 16)) { + adc = stellaris_adc_init(0x40038000, pic[14]); + } else { + adc = NULL; + } + for (i = 0; i < 4; i++) { + if (board->dc2 & (0x10000 << i)) { + stellaris_gptm_init(0x40030000 + i * 0x1000, + pic[timer_irq[i]], adc); + } + } + + stellaris_sys_init(0x400fe000, pic[28], board); + + for (i = 0; i < 7; i++) { + if (board->dc4 & (1 << i)) { + gpio_in[i] = pl061_init(gpio_addr[i], pic[gpio_irq[i]], + &gpio_out[i]); + } + } + + if (board->dc2 & (1 << 12)) { + i2c = i2c_init_bus(); + stellaris_i2c_init(0x40020000, pic[8], i2c); + if (board->oled == OLED_I2C) { + ssd0303_init(ds, i2c, 0x3d); + } + } + + for (i = 0; i < 4; i++) { + if (board->dc2 & (1 << i)) { + pl011_init(0x4000c000 + i * 0x1000, pic[uart_irq[i]], + serial_hds[i], PL011_LUMINARY); + } + } + if (board->dc2 & (1 << 4)) { + if (board->oled == OLED_SSI) { + void * oled; + /* FIXME: Implement chip select for OLED/MMC. */ + oled = ssd0323_init(ds, &gpio_out[2][7]); + pl022_init(0x40008000, pic[7], ssd0323_xfer_ssi, oled); + } else { + pl022_init(0x40008000, pic[7], NULL, NULL); + } + } +} + +/* FIXME: Figure out how to generate these from stellaris_boards. */ +static void lm3s811evb_init(int ram_size, int vga_ram_size, + const char *boot_device, DisplayState *ds, + const char **fd_filename, int snapshot, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + stellaris_init(kernel_filename, cpu_model, ds, &stellaris_boards[0]); +} + +static void lm3s6965evb_init(int ram_size, int vga_ram_size, + const char *boot_device, DisplayState *ds, + const char **fd_filename, int snapshot, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + stellaris_init(kernel_filename, cpu_model, ds, &stellaris_boards[1]); +} + +QEMUMachine lm3s811evb_machine = { + "lm3s811evb", + "Stellaris LM3S811EVB", + lm3s811evb_init, +}; + +QEMUMachine lm3s6965evb_machine = { + "lm3s6965evb", + "Stellaris LM3S6965EVB", + lm3s6965evb_init, +}; diff --git a/hw/versatilepb.c b/hw/versatilepb.c index fc27c4688d..4e8e76e26e 100644 --- a/hw/versatilepb.c +++ b/hw/versatilepb.c @@ -208,10 +208,10 @@ static void versatile_init(int ram_size, int vga_ram_size, } } - pl011_init(0x101f1000, pic[12], serial_hds[0]); - pl011_init(0x101f2000, pic[13], serial_hds[1]); - pl011_init(0x101f3000, pic[14], serial_hds[2]); - pl011_init(0x10009000, sic[6], serial_hds[3]); + pl011_init(0x101f1000, pic[12], serial_hds[0], PL011_ARM); + pl011_init(0x101f2000, pic[13], serial_hds[1], PL011_ARM); + pl011_init(0x101f3000, pic[14], serial_hds[2], PL011_ARM); + pl011_init(0x10009000, sic[6], serial_hds[3], PL011_ARM); pl080_init(0x10130000, pic[17], 8); sp804_init(0x101e2000, pic[4]); |