diff options
author | bellard <bellard@c046a42c-6fe2-441c-8c8c-71466251a162> | 2005-11-26 10:38:39 +0000 |
---|---|---|
committer | bellard <bellard@c046a42c-6fe2-441c-8c8c-71466251a162> | 2005-11-26 10:38:39 +0000 |
commit | b5ff1b3127119aa430a6fd309591d584803b7b6e (patch) | |
tree | 5857296f0bebe0d8ee9e803b60a79d277493b7e0 /hw/integratorcp.c | |
parent | 0e43e99c045eb22415a7e52e2f88dbdb8e2d96f5 (diff) |
ARM system emulation (Paul Brook)
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@1661 c046a42c-6fe2-441c-8c8c-71466251a162
Diffstat (limited to 'hw/integratorcp.c')
-rw-r--r-- | hw/integratorcp.c | 1232 |
1 files changed, 1232 insertions, 0 deletions
diff --git a/hw/integratorcp.c b/hw/integratorcp.c new file mode 100644 index 0000000000..957a94389c --- /dev/null +++ b/hw/integratorcp.c @@ -0,0 +1,1232 @@ +/* + * ARM Integrator CP System emulation. + * + * Copyright (c) 2005 CodeSourcery, LLC. + * Written by Paul Brook + * + * This code is licenced under the GPL + */ + +#include <vl.h> + +#define KERNEL_ARGS_ADDR 0x100 +#define KERNEL_LOAD_ADDR 0x00010000 +#define INITRD_LOAD_ADDR 0x00800000 + +/* Stub functions for hardware that doesn't exist. */ +void pic_set_irq(int irq, int level) +{ + cpu_abort (cpu_single_env, "pic_set_irq"); +} + +void pic_info(void) +{ +} + +void irq_info(void) +{ +} + +void vga_update_display(void) +{ +} + +void vga_screen_dump(const char *filename) +{ +} + +void vga_invalidate_display(void) +{ +} + +void DMA_run (void) +{ +} + +typedef struct { + uint32_t flash_offset; + uint32_t cm_osc; + uint32_t cm_ctrl; + uint32_t cm_lock; + uint32_t cm_auxosc; + uint32_t cm_sdram; + uint32_t cm_init; + uint32_t cm_flags; + uint32_t cm_nvflags; + uint32_t int_level; + uint32_t irq_enabled; + uint32_t fiq_enabled; +} integratorcm_state; + +static uint8_t integrator_spd[128] = { + 128, 8, 4, 11, 9, 1, 64, 0, 2, 0xa0, 0xa0, 0, 0, 8, 0, 1, + 0xe, 4, 0x1c, 1, 2, 0x20, 0xc0, 0, 0, 0, 0, 0x30, 0x28, 0x30, 0x28, 0x40 +}; + +static uint32_t integratorcm_read(void *opaque, target_phys_addr_t offset) +{ + integratorcm_state *s = (integratorcm_state *)opaque; + offset -= 0x10000000; + if (offset >= 0x100 && offset < 0x200) { + /* CM_SPD */ + if (offset >= 0x180) + return 0; + return integrator_spd[offset >> 2]; + } + switch (offset >> 2) { + case 0: /* CM_ID */ + return 0x411a3001; + case 1: /* CM_PROC */ + return 0; + case 2: /* CM_OSC */ + return s->cm_osc; + case 3: /* CM_CTRL */ + return s->cm_ctrl; + case 4: /* CM_STAT */ + return 0x00100000; + case 5: /* CM_LOCK */ + if (s->cm_lock == 0xa05f) { + return 0x1a05f; + } else { + return s->cm_lock; + } + case 6: /* CM_LMBUSCNT */ + /* ??? High frequency timer. */ + cpu_abort(cpu_single_env, "integratorcm_read: CM_LMBUSCNT"); + case 7: /* CM_AUXOSC */ + return s->cm_auxosc; + case 8: /* CM_SDRAM */ + return s->cm_sdram; + case 9: /* CM_INIT */ + return s->cm_init; + case 10: /* CM_REFCT */ + /* ??? High frequency timer. */ + cpu_abort(cpu_single_env, "integratorcm_read: CM_REFCT"); + case 12: /* CM_FLAGS */ + return s->cm_flags; + case 14: /* CM_NVFLAGS */ + return s->cm_nvflags; + case 16: /* CM_IRQ_STAT */ + return s->int_level & s->irq_enabled; + case 17: /* CM_IRQ_RSTAT */ + return s->int_level; + case 18: /* CM_IRQ_ENSET */ + return s->irq_enabled; + case 20: /* CM_SOFT_INTSET */ + return s->int_level & 1; + case 24: /* CM_FIQ_STAT */ + return s->int_level & s->fiq_enabled; + case 25: /* CM_FIQ_RSTAT */ + return s->int_level; + case 26: /* CM_FIQ_ENSET */ + return s->fiq_enabled; + case 32: /* CM_VOLTAGE_CTL0 */ + case 33: /* CM_VOLTAGE_CTL1 */ + case 34: /* CM_VOLTAGE_CTL2 */ + case 35: /* CM_VOLTAGE_CTL3 */ + /* ??? Voltage control unimplemented. */ + return 0; + default: + cpu_abort (cpu_single_env, + "integratorcm_read: Unimplemented offset 0x%x\n", offset); + return 0; + } +} + +static void integratorcm_do_remap(integratorcm_state *s, int flash) +{ + if (flash) { + cpu_register_physical_memory(0, 0x100000, IO_MEM_RAM); + } else { + cpu_register_physical_memory(0, 0x100000, s->flash_offset | IO_MEM_RAM); + } + //??? tlb_flush (cpu_single_env, 1); +} + +static void integratorcm_set_ctrl(integratorcm_state *s, uint32_t value) +{ + if (value & 8) { + cpu_abort(cpu_single_env, "Board reset\n"); + } + if ((s->cm_init ^ value) & 4) { + integratorcm_do_remap(s, (value & 4) == 0); + } + if ((s->cm_init ^ value) & 1) { + printf("Green LED %s\n", (value & 1) ? "on" : "off"); + } + s->cm_init = (s->cm_init & ~ 5) | (value ^ 5); +} + +static void integratorcm_update(integratorcm_state *s) +{ + /* ??? The CPU irq/fiq is raised when either the core module or base PIC + are active. */ + if (s->int_level & (s->irq_enabled | s->fiq_enabled)) + cpu_abort(cpu_single_env, "Core module interrupt\n"); +} + +static void integratorcm_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + integratorcm_state *s = (integratorcm_state *)opaque; + offset -= 0x10000000; + switch (offset >> 2) { + case 2: /* CM_OSC */ + if (s->cm_lock == 0xa05f) + s->cm_osc = value; + break; + case 3: /* CM_CTRL */ + integratorcm_set_ctrl(s, value); + break; + case 5: /* CM_LOCK */ + s->cm_lock = value & 0xffff; + break; + case 7: /* CM_AUXOSC */ + if (s->cm_lock == 0xa05f) + s->cm_auxosc = value; + break; + case 8: /* CM_SDRAM */ + s->cm_sdram = value; + break; + case 9: /* CM_INIT */ + /* ??? This can change the memory bus frequency. */ + s->cm_init = value; + break; + case 12: /* CM_FLAGSS */ + s->cm_flags |= value; + break; + case 13: /* CM_FLAGSC */ + s->cm_flags &= ~value; + break; + case 14: /* CM_NVFLAGSS */ + s->cm_nvflags |= value; + break; + case 15: /* CM_NVFLAGSS */ + s->cm_nvflags &= ~value; + break; + case 18: /* CM_IRQ_ENSET */ + s->irq_enabled |= value; + integratorcm_update(s); + break; + case 19: /* CM_IRQ_ENCLR */ + s->irq_enabled &= ~value; + integratorcm_update(s); + break; + case 20: /* CM_SOFT_INTSET */ + s->int_level |= (value & 1); + integratorcm_update(s); + break; + case 21: /* CM_SOFT_INTCLR */ + s->int_level &= ~(value & 1); + integratorcm_update(s); + break; + case 26: /* CM_FIQ_ENSET */ + s->fiq_enabled |= value; + integratorcm_update(s); + break; + case 27: /* CM_FIQ_ENCLR */ + s->fiq_enabled &= ~value; + integratorcm_update(s); + break; + case 32: /* CM_VOLTAGE_CTL0 */ + case 33: /* CM_VOLTAGE_CTL1 */ + case 34: /* CM_VOLTAGE_CTL2 */ + case 35: /* CM_VOLTAGE_CTL3 */ + /* ??? Voltage control unimplemented. */ + break; + default: + cpu_abort (cpu_single_env, + "integratorcm_write: Unimplemented offset 0x%x\n", offset); + break; + } +} + +/* Integrator/CM control registers. */ + +static CPUReadMemoryFunc *integratorcm_readfn[] = { + integratorcm_read, + integratorcm_read, + integratorcm_read +}; + +static CPUWriteMemoryFunc *integratorcm_writefn[] = { + integratorcm_write, + integratorcm_write, + integratorcm_write +}; + +static void integratorcm_init(int memsz, uint32_t flash_offset) +{ + int iomemtype; + integratorcm_state *s; + + s = (integratorcm_state *)qemu_mallocz(sizeof(integratorcm_state)); + s->cm_osc = 0x01000048; + /* ??? What should the high bits of this value be? */ + s->cm_auxosc = 0x0007feff; + s->cm_sdram = 0x00011122; + if (memsz >= 256) { + integrator_spd[31] = 64; + s->cm_sdram |= 0x10; + } else if (memsz >= 128) { + integrator_spd[31] = 32; + s->cm_sdram |= 0x0c; + } else if (memsz >= 64) { + integrator_spd[31] = 16; + s->cm_sdram |= 0x08; + } else if (memsz >= 32) { + integrator_spd[31] = 4; + s->cm_sdram |= 0x04; + } else { + integrator_spd[31] = 2; + } + memcpy(integrator_spd + 73, "QEMU-MEMORY", 11); + s->cm_init = 0x00000112; + s->flash_offset = flash_offset; + + iomemtype = cpu_register_io_memory(0, integratorcm_readfn, + integratorcm_writefn, s); + cpu_register_physical_memory(0x10000000, 0x007fffff, iomemtype); + integratorcm_do_remap(s, 1); + /* ??? Save/restore. */ +} + +/* Integrator/CP hardware emulation. */ +/* Primary interrupt controller. */ + +typedef struct icp_pic_state +{ + uint32_t base; + uint32_t level; + uint32_t irq_enabled; + uint32_t fiq_enabled; + void *parent; + /* -1 if parent is a cpu, otherwise IRQ number on parent PIC. */ + int parent_irq; +} icp_pic_state; + +static void icp_pic_set_level(icp_pic_state *, int, int); + +static void icp_pic_update(icp_pic_state *s) +{ + CPUState *env; + if (s->parent_irq != -1) { + uint32_t flags; + + flags = (s->level & s->irq_enabled); + icp_pic_set_level((icp_pic_state *)s->parent, s->parent_irq, + flags != 0); + return; + } + /* Raise CPU interrupt. */ + env = (CPUState *)s->parent; + if (s->level & s->fiq_enabled) { + cpu_interrupt (env, CPU_INTERRUPT_FIQ); + } else { + cpu_reset_interrupt (env, CPU_INTERRUPT_FIQ); + } + if (s->level & s->irq_enabled) { + cpu_interrupt (env, CPU_INTERRUPT_HARD); + } else { + cpu_reset_interrupt (env, CPU_INTERRUPT_HARD); + } +} + +static void icp_pic_set_level(icp_pic_state *s, int n, int level) +{ + if (level) + s->level |= 1 << n; + else + s->level &= ~(1 << n); + icp_pic_update(s); +} + +static uint32_t icp_pic_read(void *opaque, target_phys_addr_t offset) +{ + icp_pic_state *s = (icp_pic_state *)opaque; + + offset -= s->base; + switch (offset >> 2) { + case 0: /* IRQ_STATUS */ + return s->level & s->irq_enabled; + case 1: /* IRQ_RAWSTAT */ + return s->level; + case 2: /* IRQ_ENABLESET */ + return s->irq_enabled; + case 4: /* INT_SOFTSET */ + return s->level & 1; + case 8: /* FRQ_STATUS */ + return s->level & s->fiq_enabled; + case 9: /* FRQ_RAWSTAT */ + return s->level; + case 10: /* FRQ_ENABLESET */ + return s->fiq_enabled; + case 3: /* IRQ_ENABLECLR */ + case 5: /* INT_SOFTCLR */ + case 11: /* FRQ_ENABLECLR */ + default: + printf ("icp_pic_read: Bad register offset 0x%x\n", offset); + return 0; + } +} + +static void icp_pic_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + icp_pic_state *s = (icp_pic_state *)opaque; + offset -= s->base; + + switch (offset >> 2) { + case 2: /* IRQ_ENABLESET */ + s->irq_enabled |= value; + break; + case 3: /* IRQ_ENABLECLR */ + s->irq_enabled &= ~value; + break; + case 4: /* INT_SOFTSET */ + if (value & 1) + icp_pic_set_level(s, 0, 1); + break; + case 5: /* INT_SOFTCLR */ + if (value & 1) + icp_pic_set_level(s, 0, 0); + break; + case 10: /* FRQ_ENABLESET */ + s->fiq_enabled |= value; + break; + case 11: /* FRQ_ENABLECLR */ + s->fiq_enabled &= ~value; + break; + case 0: /* IRQ_STATUS */ + case 1: /* IRQ_RAWSTAT */ + case 8: /* FRQ_STATUS */ + case 9: /* FRQ_RAWSTAT */ + default: + printf ("icp_pic_write: Bad register offset 0x%x\n", offset); + return; + } + icp_pic_update(s); +} + +static CPUReadMemoryFunc *icp_pic_readfn[] = { + icp_pic_read, + icp_pic_read, + icp_pic_read +}; + +static CPUWriteMemoryFunc *icp_pic_writefn[] = { + icp_pic_write, + icp_pic_write, + icp_pic_write +}; + +static icp_pic_state *icp_pic_init(uint32_t base, void *parent, + int parent_irq) +{ + icp_pic_state *s; + int iomemtype; + + s = (icp_pic_state *)qemu_mallocz(sizeof(icp_pic_state)); + if (!s) + return NULL; + + s->base = base; + s->parent = parent; + s->parent_irq = parent_irq; + iomemtype = cpu_register_io_memory(0, icp_pic_readfn, + icp_pic_writefn, s); + cpu_register_physical_memory(base, 0x007fffff, iomemtype); + /* ??? Save/restore. */ + return s; +} + +/* Timers. */ + +/* System bus clock speed (40MHz) for timer 0. Not sure about this value. */ +#define ICP_BUS_FREQ 40000000 + +typedef struct { + int64_t next_time; + int64_t expires[3]; + int64_t loaded[3]; + QEMUTimer *timer; + icp_pic_state *pic; + uint32_t base; + uint32_t control[3]; + uint32_t count[3]; + uint32_t limit[3]; + int freq[3]; + int int_level[3]; +} icp_pit_state; + +/* Calculate the new expiry time of the given timer. */ + +static void icp_pit_reload(icp_pit_state *s, int n) +{ + int64_t delay; + + s->loaded[n] = s->expires[n]; + delay = muldiv64(s->count[n], ticks_per_sec, s->freq[n]); + if (delay == 0) + delay = 1; + s->expires[n] += delay; +} + +/* Check all active timers, and schedule the next timer interrupt. */ + +static void icp_pit_update(icp_pit_state *s, int64_t now) +{ + int n; + int64_t next; + + next = now; + for (n = 0; n < 3; n++) { + /* Ignore disabled timers. */ + if ((s->control[n] & 0x80) == 0) + continue; + /* Ignore expired one-shot timers. */ + if (s->count[n] == 0 && s->control[n] & 1) + continue; + if (s->expires[n] - now <= 0) { + /* Timer has expired. */ + s->int_level[n] = 1; + if (s->control[n] & 1) { + /* One-shot. */ + s->count[n] = 0; + } else { + if ((s->control[n] & 0x40) == 0) { + /* Free running. */ + if (s->control[n] & 2) + s->count[n] = 0xffffffff; + else + s->count[n] = 0xffff; + } else { + /* Periodic. */ + s->count[n] = s->limit[n]; + } + } + } + while (s->expires[n] - now <= 0) { + icp_pit_reload(s, n); + } + } + /* Update interrupts. */ + for (n = 0; n < 3; n++) { + if (s->int_level[n] && (s->control[n] & 0x20)) { + icp_pic_set_level(s->pic, 5 + n, 1); + } else { + icp_pic_set_level(s->pic, 5 + n, 0); + } + if (next - s->expires[n] < 0) + next = s->expires[n]; + } + /* Schedule the next timer interrupt. */ + if (next == now) { + qemu_del_timer(s->timer); + s->next_time = 0; + } else if (next != s->next_time) { + qemu_mod_timer(s->timer, next); + s->next_time = next; + } +} + +/* Return the current value of the timer. */ +static uint32_t icp_pit_getcount(icp_pit_state *s, int n, int64_t now) +{ + int64_t elapsed; + int64_t period; + + if (s->count[n] == 0) + return 0; + if ((s->control[n] & 0x80) == 0) + return s->count[n]; + elapsed = now - s->loaded[n]; + period = s->expires[n] - s->loaded[n]; + /* If the timer should have expired then return 0. This can happen + when the host timer signal doesnt occur immediately. It's better to + have a timer appear to sit at zero for a while than have it wrap + around before the guest interrupt is raised. */ + /* ??? Could we trigger the interrupt here? */ + if (elapsed > period) + return 0; + /* We need to calculate count * elapsed / period without overfowing. + Scale both elapsed and period so they fit in a 32-bit int. */ + while (period != (int32_t)period) { + period >>= 1; + elapsed >>= 1; + } + return ((uint64_t)s->count[n] * (uint64_t)(int32_t)elapsed) + / (int32_t)period; +} + +static uint32_t icp_pit_read(void *opaque, target_phys_addr_t offset) +{ + int n; + icp_pit_state *s = (icp_pit_state *)opaque; + + offset -= s->base; + n = offset >> 8; + if (n > 2) + cpu_abort (cpu_single_env, "icp_pit_read: Bad timer %x\n", offset); + switch ((offset & 0xff) >> 2) { + case 0: /* TimerLoad */ + case 6: /* TimerBGLoad */ + return s->limit[n]; + case 1: /* TimerValue */ + return icp_pit_getcount(s, n, qemu_get_clock(vm_clock)); + case 2: /* TimerControl */ + return s->control[n]; + case 4: /* TimerRIS */ + return s->int_level[n]; + case 5: /* TimerMIS */ + if ((s->control[n] & 0x20) == 0) + return 0; + return s->int_level[n]; + default: + cpu_abort (cpu_single_env, "icp_pit_read: Bad offset %x\n", offset); + return 0; + } +} + +static void icp_pit_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + icp_pit_state *s = (icp_pit_state *)opaque; + int n; + int64_t now; + + now = qemu_get_clock(vm_clock); + offset -= s->base; + n = offset >> 8; + if (n > 2) + cpu_abort (cpu_single_env, "icp_pit_write: Bad offset %x\n", offset); + + switch ((offset & 0xff) >> 2) { + case 0: /* TimerLoad */ + s->limit[n] = value; + s->count[n] = value; + s->expires[n] = now; + icp_pit_reload(s, n); + break; + case 1: /* TimerValue */ + /* ??? Linux seems to want to write to this readonly register. + Ignore it. */ + break; + case 2: /* TimerControl */ + if (s->control[n] & 0x80) { + /* Pause the timer if it is running. This may cause some + inaccuracy dure to rounding, but avoids a whole lot of other + messyness. */ + s->count[n] = icp_pit_getcount(s, n, now); + } + s->control[n] = value; + if (n == 0) + s->freq[n] = ICP_BUS_FREQ; + else + s->freq[n] = 1000000; + /* ??? Need to recalculate expiry time after changing divisor. */ + switch ((value >> 2) & 3) { + case 1: s->freq[n] >>= 4; break; + case 2: s->freq[n] >>= 8; break; + } + if (s->control[n] & 0x80) { + /* Restart the timer if still enabled. */ + s->expires[n] = now; + icp_pit_reload(s, n); + } + break; + case 3: /* TimerIntClr */ + s->int_level[n] = 0; + break; + case 6: /* TimerBGLoad */ + s->limit[n] = value; + break; + default: + cpu_abort (cpu_single_env, "icp_pit_write: Bad offset %x\n", offset); + } + icp_pit_update(s, now); +} + +static void icp_pit_tick(void *opaque) +{ + int64_t now; + + now = qemu_get_clock(vm_clock); + icp_pit_update((icp_pit_state *)opaque, now); +} + +static CPUReadMemoryFunc *icp_pit_readfn[] = { + icp_pit_read, + icp_pit_read, + icp_pit_read +}; + +static CPUWriteMemoryFunc *icp_pit_writefn[] = { + icp_pit_write, + icp_pit_write, + icp_pit_write +}; + +static void icp_pit_init(uint32_t base, icp_pic_state *pic) +{ + int iomemtype; + icp_pit_state *s; + int n; + + s = (icp_pit_state *)qemu_mallocz(sizeof(icp_pit_state)); + s->base = base; + s->pic = pic; + s->freq[0] = ICP_BUS_FREQ; + s->freq[1] = 1000000; + s->freq[2] = 1000000; + for (n = 0; n < 3; n++) { + s->control[n] = 0x20; + s->count[n] = 0xffffffff; + } + + iomemtype = cpu_register_io_memory(0, icp_pit_readfn, + icp_pit_writefn, s); + cpu_register_physical_memory(base, 0x007fffff, iomemtype); + s->timer = qemu_new_timer(vm_clock, icp_pit_tick, s); + /* ??? Save/restore. */ +} + +/* ARM PrimeCell PL011 UART */ + +typedef struct { + uint32_t base; + uint32_t readbuff; + uint32_t flags; + uint32_t lcr; + uint32_t cr; + uint32_t dmacr; + uint32_t int_enabled; + uint32_t int_level; + uint32_t read_fifo[16]; + uint32_t ilpr; + uint32_t ibrd; + uint32_t fbrd; + uint32_t ifl; + int read_pos; + int read_count; + int read_trigger; + CharDriverState *chr; + icp_pic_state *pic; + int irq; +} pl011_state; + +#define PL011_INT_TX 0x20 +#define PL011_INT_RX 0x10 + +#define PL011_FLAG_TXFE 0x80 +#define PL011_FLAG_RXFF 0x40 +#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 void pl011_update(pl011_state *s) +{ + uint32_t flags; + + flags = s->int_level & s->int_enabled; + icp_pic_set_level(s->pic, s->irq, flags != 0); +} + +static uint32_t pl011_read(void *opaque, target_phys_addr_t offset) +{ + pl011_state *s = (pl011_state *)opaque; + uint32_t c; + + offset -= s->base; + if (offset >= 0xfe0 && offset < 0x1000) { + return pl011_id[(offset - 0xfe0) >> 2]; + } + switch (offset >> 2) { + case 0: /* UARTDR */ + s->flags &= ~PL011_FLAG_RXFF; + c = s->read_fifo[s->read_pos]; + if (s->read_count > 0) { + s->read_count--; + if (++s->read_pos == 16) + s->read_pos = 0; + } + if (s->read_count == 0) { + s->flags |= PL011_FLAG_RXFE; + } + if (s->read_count == s->read_trigger - 1) + s->int_level &= ~ PL011_INT_RX; + pl011_update(s); + return c; + case 1: /* UARTCR */ + return 0; + case 6: /* UARTFR */ + return s->flags; + case 8: /* UARTILPR */ + return s->ilpr; + case 9: /* UARTIBRD */ + return s->ibrd; + case 10: /* UARTFBRD */ + return s->fbrd; + case 11: /* UARTLCR_H */ + return s->lcr; + case 12: /* UARTCR */ + return s->cr; + case 13: /* UARTIFLS */ + return s->ifl; + case 14: /* UARTIMSC */ + return s->int_enabled; + case 15: /* UARTRIS */ + return s->int_level; + case 16: /* UARTMIS */ + return s->int_level & s->int_enabled; + case 18: /* UARTDMACR */ + return s->dmacr; + default: + cpu_abort (cpu_single_env, "pl011_read: Bad offset %x\n", offset); + return 0; + } +} + +static void pl011_set_read_trigger(pl011_state *s) +{ +#if 0 + /* The docs say the RX interrupt is triggered when the FIFO exceeds + the threshold. However linux only reads the FIFO in response to an + interrupt. Triggering the interrupt when the FIFO is non-empty seems + to make things work. */ + if (s->lcr & 0x10) + s->read_trigger = (s->ifl >> 1) & 0x1c; + else +#endif + s->read_trigger = 1; +} + +static void pl011_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + pl011_state *s = (pl011_state *)opaque; + unsigned char ch; + + offset -= s->base; + switch (offset >> 2) { + case 0: /* UARTDR */ + /* ??? Check if transmitter is enabled. */ + ch = value; + if (s->chr) + qemu_chr_write(s->chr, &ch, 1); + s->int_level |= PL011_INT_TX; + pl011_update(s); + break; + case 1: /* UARTCR */ + s->cr = value; + break; + case 8: /* UARTUARTILPR */ + s->ilpr = value; + break; + case 9: /* UARTIBRD */ + s->ibrd = value; + break; + case 10: /* UARTFBRD */ + s->fbrd = value; + break; + case 11: /* UARTLCR_H */ + s->lcr = value; + pl011_set_read_trigger(s); + break; + case 12: /* UARTCR */ + /* ??? Need to implement the enable and loopback bits. */ + s->cr = value; + break; + case 13: /* UARTIFS */ + s->ifl = value; + pl011_set_read_trigger(s); + break; + case 14: /* UARTIMSC */ + s->int_enabled = value; + pl011_update(s); + break; + case 17: /* UARTICR */ + s->int_level &= ~value; + pl011_update(s); + break; + case 18: /* UARTDMACR */ + s->dmacr = value; + if (value & 3) + cpu_abort(cpu_single_env, "PL011: DMA not implemented\n"); + break; + default: + cpu_abort (cpu_single_env, "pl011_write: Bad offset %x\n", offset); + } +} + +static int pl011_can_recieve(void *opaque) +{ + pl011_state *s = (pl011_state *)opaque; + + if (s->lcr & 0x10) + return s->read_count < 16; + else + return s->read_count < 1; +} + +static void pl011_recieve(void *opaque, const uint8_t *buf, int size) +{ + pl011_state *s = (pl011_state *)opaque; + int slot; + + slot = s->read_pos + s->read_count; + if (slot >= 16) + slot -= 16; + s->read_fifo[slot] = *buf; + s->read_count++; + s->flags &= ~PL011_FLAG_RXFE; + if (s->cr & 0x10 || s->read_count == 16) { + s->flags |= PL011_FLAG_RXFF; + } + if (s->read_count == s->read_trigger) { + s->int_level |= PL011_INT_RX; + pl011_update(s); + } +} + +static void pl011_event(void *opaque, int event) +{ + /* ??? Should probably implement break. */ +} + +static CPUReadMemoryFunc *pl011_readfn[] = { + pl011_read, + pl011_read, + pl011_read +}; + +static CPUWriteMemoryFunc *pl011_writefn[] = { + pl011_write, + pl011_write, + pl011_write +}; + +static void pl011_init(uint32_t base, icp_pic_state *pic, int irq, + CharDriverState *chr) +{ + int iomemtype; + pl011_state *s; + + s = (pl011_state *)qemu_mallocz(sizeof(pl011_state)); + iomemtype = cpu_register_io_memory(0, pl011_readfn, + pl011_writefn, s); + cpu_register_physical_memory(base, 0x007fffff, iomemtype); + s->base = base; + s->pic = pic; + s->irq = irq; + s->chr = chr; + s->read_trigger = 1; + s->ifl = 0x12; + s->cr = 0x300; + s->flags = 0x90; + if (chr){ + qemu_chr_add_read_handler(chr, pl011_can_recieve, pl011_recieve, s); + qemu_chr_add_event_handler(chr, pl011_event); + } + /* ??? Save/restore. */ +} + +/* CP control registers. */ +typedef struct { + uint32_t base; +} icp_control_state; + +static uint32_t icp_control_read(void *opaque, target_phys_addr_t offset) +{ + icp_control_state *s = (icp_control_state *)opaque; + offset -= s->base; + switch (offset >> 2) { + case 0: /* CP_IDFIELD */ + return 0x41034003; + case 1: /* CP_FLASHPROG */ + return 0; + case 2: /* CP_INTREG */ + return 0; + case 3: /* CP_DECODE */ + return 0x11; + default: + cpu_abort (cpu_single_env, "icp_control_read: Bad offset %x\n", offset); + return 0; + } +} + +static void icp_control_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + icp_control_state *s = (icp_control_state *)opaque; + offset -= s->base; + switch (offset >> 2) { + case 1: /* CP_FLASHPROG */ + case 2: /* CP_INTREG */ + case 3: /* CP_DECODE */ + /* Nothing interesting implemented yet. */ + break; + default: + cpu_abort (cpu_single_env, "icp_control_write: Bad offset %x\n", offset); + } +} +static CPUReadMemoryFunc *icp_control_readfn[] = { + icp_control_read, + icp_control_read, + icp_control_read +}; + +static CPUWriteMemoryFunc *icp_control_writefn[] = { + icp_control_write, + icp_control_write, + icp_control_write +}; + +static void icp_control_init(uint32_t base) +{ + int iomemtype; + icp_control_state *s; + + s = (icp_control_state *)qemu_mallocz(sizeof(icp_control_state)); + iomemtype = cpu_register_io_memory(0, icp_control_readfn, + icp_control_writefn, s); + cpu_register_physical_memory(base, 0x007fffff, iomemtype); + s->base = base; + /* ??? Save/restore. */ +} + + +/* Keyboard/Mouse Interface. */ + +typedef struct { + void *dev; + uint32_t base; + uint32_t cr; + uint32_t clk; + uint32_t last; + icp_pic_state *pic; + int pending; + int irq; + int is_mouse; +} icp_kmi_state; + +static void icp_kmi_update(void *opaque, int level) +{ + icp_kmi_state *s = (icp_kmi_state *)opaque; + int raise; + + s->pending = level; + raise = (s->pending && (s->cr & 0x10) != 0) + || (s->cr & 0x08) != 0; + icp_pic_set_level(s->pic, s->irq, raise); +} + +static uint32_t icp_kmi_read(void *opaque, target_phys_addr_t offset) +{ + icp_kmi_state *s = (icp_kmi_state *)opaque; + offset -= s->base; + if (offset >= 0xfe0 && offset < 0x1000) + return 0; + + switch (offset >> 2) { + case 0: /* KMICR */ + return s->cr; + case 1: /* KMISTAT */ + /* KMIC and KMID bits not implemented. */ + if (s->pending) { + return 0x10; + } else { + return 0; + } + case 2: /* KMIDATA */ + if (s->pending) + s->last = ps2_read_data(s->dev); + return s->last; + case 3: /* KMICLKDIV */ + return s->clk; + case 4: /* KMIIR */ + return s->pending | 2; + default: + cpu_abort (cpu_single_env, "icp_kmi_read: Bad offset %x\n", offset); + return 0; + } +} + +static void icp_kmi_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + icp_kmi_state *s = (icp_kmi_state *)opaque; + offset -= s->base; + switch (offset >> 2) { + case 0: /* KMICR */ + s->cr = value; + icp_kmi_update(s, s->pending); + /* ??? Need to implement the enable/disable bit. */ + break; + case 2: /* KMIDATA */ + /* ??? This should toggle the TX interrupt line. */ + /* ??? This means kbd/mouse can block each other. */ + if (s->is_mouse) { + ps2_write_mouse(s->dev, value); + } else { + ps2_write_keyboard(s->dev, value); + } + break; + case 3: /* KMICLKDIV */ + s->clk = value; + return; + default: + cpu_abort (cpu_single_env, "icp_kmi_write: Bad offset %x\n", offset); + } +} +static CPUReadMemoryFunc *icp_kmi_readfn[] = { + icp_kmi_read, + icp_kmi_read, + icp_kmi_read +}; + +static CPUWriteMemoryFunc *icp_kmi_writefn[] = { + icp_kmi_write, + icp_kmi_write, + icp_kmi_write +}; + +static void icp_kmi_init(uint32_t base, icp_pic_state * pic, int irq, + int is_mouse) +{ + int iomemtype; + icp_kmi_state *s; + + s = (icp_kmi_state *)qemu_mallocz(sizeof(icp_kmi_state)); + iomemtype = cpu_register_io_memory(0, icp_kmi_readfn, + icp_kmi_writefn, s); + cpu_register_physical_memory(base, 0x007fffff, iomemtype); + s->base = base; + s->pic = pic; + s->irq = irq; + s->is_mouse = is_mouse; + if (is_mouse) + s->dev = ps2_mouse_init(icp_kmi_update, s); + else + s->dev = ps2_kbd_init(icp_kmi_update, s); + /* ??? Save/restore. */ +} + +/* The worlds second smallest bootloader. Set r0-r2, then jump to kernel. */ +static uint32_t bootloader[] = { + 0xe3a00000, /* mov r0, #0 */ + 0xe3a01013, /* mov r1, #0x13 */ + 0xe3811c01, /* orr r1, r1, #0x100 */ + 0xe59f2000, /* ldr r2, [pc, #0] */ + 0xe59ff000, /* ldr pc, [pc, #0] */ + 0, /* Address of kernel args. Set by integratorcp_init. */ + 0 /* Kernel entry point. Set by integratorcp_init. */ +}; + +static void set_kernel_args(uint32_t ram_size, int initrd_size, + const char *kernel_cmdline) +{ + uint32_t *p; + + p = (uint32_t *)(phys_ram_base + KERNEL_ARGS_ADDR); + /* ATAG_CORE */ + *(p++) = 5; + *(p++) = 0x54410001; + *(p++) = 1; + *(p++) = 0x1000; + *(p++) = 0; + /* ATAG_MEM */ + *(p++) = 4; + *(p++) = 0x54410002; + *(p++) = ram_size; + *(p++) = 0; + if (initrd_size) { + /* ATAG_INITRD2 */ + *(p++) = 4; + *(p++) = 0x54420005; + *(p++) = INITRD_LOAD_ADDR; + *(p++) = initrd_size; + } + if (kernel_cmdline && *kernel_cmdline) { + /* ATAG_CMDLINE */ + int cmdline_size; + + cmdline_size = strlen(kernel_cmdline); + memcpy (p + 2, kernel_cmdline, cmdline_size + 1); + cmdline_size = (cmdline_size >> 2) + 1; + *(p++) = cmdline_size + 2; + *(p++) = 0x54410009; + p += cmdline_size; + } + /* ATAG_END */ + *(p++) = 0; + *(p++) = 0; +} + +/* Board init. */ + +static void integratorcp_init(int ram_size, int vga_ram_size, int boot_device, + DisplayState *ds, const char **fd_filename, int snapshot, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename) +{ + CPUState *env; + uint32_t bios_offset; + icp_pic_state *pic; + int kernel_size; + int initrd_size; + + env = cpu_init(); + bios_offset = ram_size + vga_ram_size; + /* ??? On a real system the first 1Mb is mapped as SSRAM or boot flash. */ + /* ??? RAM shoud repeat to fill physical memory space. */ + /* SDRAM at address zero*/ + cpu_register_physical_memory(0, ram_size, IO_MEM_RAM); + /* And again at address 0x80000000 */ + cpu_register_physical_memory(0x80000000, ram_size, IO_MEM_RAM); + + integratorcm_init(ram_size >> 20, bios_offset); + pic = icp_pic_init(0x14000000, env, -1); + icp_pic_init(0xca000000, pic, 26); + icp_pit_init(0x13000000, pic); + pl011_init(0x16000000, pic, 1, serial_hds[0]); + pl011_init(0x17000000, pic, 2, serial_hds[1]); + icp_control_init(0xcb000000); + icp_kmi_init(0x18000000, pic, 3, 0); + icp_kmi_init(0x19000000, pic, 4, 1); + + /* Load the kernel. */ + if (!kernel_filename) { + fprintf(stderr, "Kernel image must be specified\n"); + exit(1); + } + kernel_size = load_image(kernel_filename, + phys_ram_base + KERNEL_LOAD_ADDR); + if (kernel_size < 0) { + fprintf(stderr, "qemu: could not load kernel '%s'\n", kernel_filename); + exit(1); + } + if (initrd_filename) { + initrd_size = load_image(initrd_filename, + phys_ram_base + INITRD_LOAD_ADDR); + if (initrd_size < 0) { + fprintf(stderr, "qemu: could not load initrd '%s'\n", + initrd_filename); + exit(1); + } + } else { + initrd_size = 0; + } + bootloader[5] = KERNEL_ARGS_ADDR; + bootloader[6] = KERNEL_LOAD_ADDR; + memcpy(phys_ram_base, bootloader, sizeof(bootloader)); + set_kernel_args(ram_size, initrd_size, kernel_cmdline); +} + +QEMUMachine integratorcp_machine = { + "integratorcp", + "ARM Integrator/CP", + integratorcp_init, +}; |