diff options
author | Yang Zhang <yang.z.zhang@intel.com> | 2015-08-28 09:58:54 +0800 |
---|---|---|
committer | Yang Zhang <yang.z.zhang@intel.com> | 2015-09-01 12:44:00 +0800 |
commit | e44e3482bdb4d0ebde2d8b41830ac2cdb07948fb (patch) | |
tree | 66b09f592c55df2878107a468a91d21506104d3f /qemu/hw/arm | |
parent | 9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00 (diff) |
Add qemu 2.4.0
Change-Id: Ic99cbad4b61f8b127b7dc74d04576c0bcbaaf4f5
Signed-off-by: Yang Zhang <yang.z.zhang@intel.com>
Diffstat (limited to 'qemu/hw/arm')
41 files changed, 26779 insertions, 0 deletions
diff --git a/qemu/hw/arm/Makefile.objs b/qemu/hw/arm/Makefile.objs new file mode 100644 index 000000000..cf346c1d0 --- /dev/null +++ b/qemu/hw/arm/Makefile.objs @@ -0,0 +1,15 @@ +obj-y += boot.o collie.o exynos4_boards.o gumstix.o highbank.o +obj-$(CONFIG_DIGIC) += digic_boards.o +obj-y += integratorcp.o kzm.o mainstone.o musicpal.o nseries.o +obj-y += omap_sx1.o palm.o realview.o spitz.o stellaris.o +obj-y += tosa.o versatilepb.o vexpress.o virt.o xilinx_zynq.o z2.o +obj-$(CONFIG_ACPI) += virt-acpi-build.o +obj-y += netduino2.o +obj-y += sysbus-fdt.o + +obj-y += armv7m.o exynos4210.o pxa2xx.o pxa2xx_gpio.o pxa2xx_pic.o +obj-$(CONFIG_DIGIC) += digic.o +obj-y += omap1.o omap2.o strongarm.o +obj-$(CONFIG_ALLWINNER_A10) += allwinner-a10.o cubieboard.o +obj-$(CONFIG_STM32F205_SOC) += stm32f205_soc.o +obj-$(CONFIG_XLNX_ZYNQMP) += xlnx-zynqmp.o xlnx-ep108.o diff --git a/qemu/hw/arm/allwinner-a10.c b/qemu/hw/arm/allwinner-a10.c new file mode 100644 index 000000000..ff249af33 --- /dev/null +++ b/qemu/hw/arm/allwinner-a10.c @@ -0,0 +1,121 @@ +/* + * Allwinner A10 SoC emulation + * + * Copyright (C) 2013 Li Guang + * Written by Li Guang <lig.fnst@cn.fujitsu.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "hw/sysbus.h" +#include "hw/devices.h" +#include "hw/arm/allwinner-a10.h" + +static void aw_a10_init(Object *obj) +{ + AwA10State *s = AW_A10(obj); + + object_initialize(&s->cpu, sizeof(s->cpu), "cortex-a8-" TYPE_ARM_CPU); + object_property_add_child(obj, "cpu", OBJECT(&s->cpu), NULL); + + object_initialize(&s->intc, sizeof(s->intc), TYPE_AW_A10_PIC); + qdev_set_parent_bus(DEVICE(&s->intc), sysbus_get_default()); + + object_initialize(&s->timer, sizeof(s->timer), TYPE_AW_A10_PIT); + qdev_set_parent_bus(DEVICE(&s->timer), sysbus_get_default()); + + object_initialize(&s->emac, sizeof(s->emac), TYPE_AW_EMAC); + qdev_set_parent_bus(DEVICE(&s->emac), sysbus_get_default()); + /* FIXME use qdev NIC properties instead of nd_table[] */ + if (nd_table[0].used) { + qemu_check_nic_model(&nd_table[0], TYPE_AW_EMAC); + qdev_set_nic_properties(DEVICE(&s->emac), &nd_table[0]); + } +} + +static void aw_a10_realize(DeviceState *dev, Error **errp) +{ + AwA10State *s = AW_A10(dev); + SysBusDevice *sysbusdev; + uint8_t i; + qemu_irq fiq, irq; + Error *err = NULL; + + object_property_set_bool(OBJECT(&s->cpu), true, "realized", &err); + if (err != NULL) { + error_propagate(errp, err); + return; + } + irq = qdev_get_gpio_in(DEVICE(&s->cpu), ARM_CPU_IRQ); + fiq = qdev_get_gpio_in(DEVICE(&s->cpu), ARM_CPU_FIQ); + + object_property_set_bool(OBJECT(&s->intc), true, "realized", &err); + if (err != NULL) { + error_propagate(errp, err); + return; + } + sysbusdev = SYS_BUS_DEVICE(&s->intc); + sysbus_mmio_map(sysbusdev, 0, AW_A10_PIC_REG_BASE); + sysbus_connect_irq(sysbusdev, 0, irq); + sysbus_connect_irq(sysbusdev, 1, fiq); + for (i = 0; i < AW_A10_PIC_INT_NR; i++) { + s->irq[i] = qdev_get_gpio_in(DEVICE(&s->intc), i); + } + + object_property_set_bool(OBJECT(&s->timer), true, "realized", &err); + if (err != NULL) { + error_propagate(errp, err); + return; + } + sysbusdev = SYS_BUS_DEVICE(&s->timer); + sysbus_mmio_map(sysbusdev, 0, AW_A10_PIT_REG_BASE); + sysbus_connect_irq(sysbusdev, 0, s->irq[22]); + sysbus_connect_irq(sysbusdev, 1, s->irq[23]); + sysbus_connect_irq(sysbusdev, 2, s->irq[24]); + sysbus_connect_irq(sysbusdev, 3, s->irq[25]); + sysbus_connect_irq(sysbusdev, 4, s->irq[67]); + sysbus_connect_irq(sysbusdev, 5, s->irq[68]); + + object_property_set_bool(OBJECT(&s->emac), true, "realized", &err); + if (err != NULL) { + error_propagate(errp, err); + return; + } + sysbusdev = SYS_BUS_DEVICE(&s->emac); + sysbus_mmio_map(sysbusdev, 0, AW_A10_EMAC_BASE); + sysbus_connect_irq(sysbusdev, 0, s->irq[55]); + + /* FIXME use a qdev chardev prop instead of serial_hds[] */ + serial_mm_init(get_system_memory(), AW_A10_UART0_REG_BASE, 2, s->irq[1], + 115200, serial_hds[0], DEVICE_NATIVE_ENDIAN); +} + +static void aw_a10_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = aw_a10_realize; +} + +static const TypeInfo aw_a10_type_info = { + .name = TYPE_AW_A10, + .parent = TYPE_DEVICE, + .instance_size = sizeof(AwA10State), + .instance_init = aw_a10_init, + .class_init = aw_a10_class_init, +}; + +static void aw_a10_register_types(void) +{ + type_register_static(&aw_a10_type_info); +} + +type_init(aw_a10_register_types) diff --git a/qemu/hw/arm/armv7m.c b/qemu/hw/arm/armv7m.c new file mode 100644 index 000000000..c6eab6de3 --- /dev/null +++ b/qemu/hw/arm/armv7m.c @@ -0,0 +1,266 @@ +/* + * ARMV7M System emulation. + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the GPL. + */ + +#include "hw/sysbus.h" +#include "hw/arm/arm.h" +#include "hw/loader.h" +#include "elf.h" +#include "sysemu/qtest.h" +#include "qemu/error-report.h" + +/* Bitbanded IO. Each word corresponds to a single bit. */ + +/* Get the byte address of the real memory for a bitband access. */ +static inline uint32_t bitband_addr(void * opaque, uint32_t addr) +{ + uint32_t res; + + res = *(uint32_t *)opaque; + res |= (addr & 0x1ffffff) >> 5; + return res; + +} + +static uint32_t bitband_readb(void *opaque, hwaddr offset) +{ + uint8_t v; + cpu_physical_memory_read(bitband_addr(opaque, offset), &v, 1); + return (v & (1 << ((offset >> 2) & 7))) != 0; +} + +static void bitband_writeb(void *opaque, hwaddr offset, + uint32_t value) +{ + uint32_t addr; + uint8_t mask; + uint8_t v; + addr = bitband_addr(opaque, 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, hwaddr offset) +{ + uint32_t addr; + uint16_t mask; + uint16_t v; + addr = bitband_addr(opaque, offset) & ~1; + mask = (1 << ((offset >> 2) & 15)); + mask = tswap16(mask); + cpu_physical_memory_read(addr, &v, 2); + return (v & mask) != 0; +} + +static void bitband_writew(void *opaque, hwaddr offset, + uint32_t value) +{ + uint32_t addr; + uint16_t mask; + uint16_t v; + addr = bitband_addr(opaque, offset) & ~1; + mask = (1 << ((offset >> 2) & 15)); + mask = tswap16(mask); + cpu_physical_memory_read(addr, &v, 2); + if (value & 1) + v |= mask; + else + v &= ~mask; + cpu_physical_memory_write(addr, &v, 2); +} + +static uint32_t bitband_readl(void *opaque, hwaddr offset) +{ + uint32_t addr; + uint32_t mask; + uint32_t v; + addr = bitband_addr(opaque, offset) & ~3; + mask = (1 << ((offset >> 2) & 31)); + mask = tswap32(mask); + cpu_physical_memory_read(addr, &v, 4); + return (v & mask) != 0; +} + +static void bitband_writel(void *opaque, hwaddr offset, + uint32_t value) +{ + uint32_t addr; + uint32_t mask; + uint32_t v; + addr = bitband_addr(opaque, offset) & ~3; + mask = (1 << ((offset >> 2) & 31)); + mask = tswap32(mask); + cpu_physical_memory_read(addr, &v, 4); + if (value & 1) + v |= mask; + else + v &= ~mask; + cpu_physical_memory_write(addr, &v, 4); +} + +static const MemoryRegionOps bitband_ops = { + .old_mmio = { + .read = { bitband_readb, bitband_readw, bitband_readl, }, + .write = { bitband_writeb, bitband_writew, bitband_writel, }, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +#define TYPE_BITBAND "ARM,bitband-memory" +#define BITBAND(obj) OBJECT_CHECK(BitBandState, (obj), TYPE_BITBAND) + +typedef struct { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion iomem; + uint32_t base; +} BitBandState; + +static int bitband_init(SysBusDevice *dev) +{ + BitBandState *s = BITBAND(dev); + + memory_region_init_io(&s->iomem, OBJECT(s), &bitband_ops, &s->base, + "bitband", 0x02000000); + sysbus_init_mmio(dev, &s->iomem); + return 0; +} + +static void armv7m_bitband_init(void) +{ + DeviceState *dev; + + dev = qdev_create(NULL, TYPE_BITBAND); + qdev_prop_set_uint32(dev, "base", 0x20000000); + qdev_init_nofail(dev); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0x22000000); + + dev = qdev_create(NULL, TYPE_BITBAND); + qdev_prop_set_uint32(dev, "base", 0x40000000); + qdev_init_nofail(dev); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0x42000000); +} + +/* Board init. */ + +static void armv7m_reset(void *opaque) +{ + ARMCPU *cpu = opaque; + + cpu_reset(CPU(cpu)); +} + +/* Init CPU and memory for a v7-M based board. + mem_size is in bytes. + Returns the NVIC array. */ + +qemu_irq *armv7m_init(MemoryRegion *system_memory, int mem_size, int num_irq, + const char *kernel_filename, const char *cpu_model) +{ + ARMCPU *cpu; + CPUARMState *env; + DeviceState *nvic; + qemu_irq *pic = g_new(qemu_irq, num_irq); + int image_size; + uint64_t entry; + uint64_t lowaddr; + int i; + int big_endian; + MemoryRegion *hack = g_new(MemoryRegion, 1); + + if (cpu_model == NULL) { + cpu_model = "cortex-m3"; + } + cpu = cpu_arm_init(cpu_model); + if (cpu == NULL) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + env = &cpu->env; + + armv7m_bitband_init(); + + nvic = qdev_create(NULL, "armv7m_nvic"); + qdev_prop_set_uint32(nvic, "num-irq", num_irq); + env->nvic = nvic; + qdev_init_nofail(nvic); + sysbus_connect_irq(SYS_BUS_DEVICE(nvic), 0, + qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ)); + for (i = 0; i < num_irq; i++) { + pic[i] = qdev_get_gpio_in(nvic, i); + } + +#ifdef TARGET_WORDS_BIGENDIAN + big_endian = 1; +#else + big_endian = 0; +#endif + + if (!kernel_filename && !qtest_enabled()) { + fprintf(stderr, "Guest image must be specified (using -kernel)\n"); + exit(1); + } + + if (kernel_filename) { + image_size = load_elf(kernel_filename, NULL, NULL, &entry, &lowaddr, + NULL, big_endian, ELF_MACHINE, 1); + if (image_size < 0) { + image_size = load_image_targphys(kernel_filename, 0, mem_size); + lowaddr = 0; + } + if (image_size < 0) { + error_report("Could not load kernel '%s'", kernel_filename); + exit(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. */ + memory_region_init_ram(hack, NULL, "armv7m.hack", 0x1000, &error_abort); + vmstate_register_ram_global(hack); + memory_region_add_subregion(system_memory, 0xfffff000, hack); + + qemu_register_reset(armv7m_reset, cpu); + return pic; +} + +static Property bitband_properties[] = { + DEFINE_PROP_UINT32("base", BitBandState, base, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void bitband_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = bitband_init; + dc->props = bitband_properties; +} + +static const TypeInfo bitband_info = { + .name = TYPE_BITBAND, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(BitBandState), + .class_init = bitband_class_init, +}; + +static void armv7m_register_types(void) +{ + type_register_static(&bitband_info); +} + +type_init(armv7m_register_types) diff --git a/qemu/hw/arm/boot.c b/qemu/hw/arm/boot.c new file mode 100644 index 000000000..5b969cda1 --- /dev/null +++ b/qemu/hw/arm/boot.c @@ -0,0 +1,805 @@ +/* + * ARM kernel loader. + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the GPL. + */ + +#include "config.h" +#include "hw/hw.h" +#include "hw/arm/arm.h" +#include "sysemu/sysemu.h" +#include "hw/boards.h" +#include "hw/loader.h" +#include "elf.h" +#include "sysemu/device_tree.h" +#include "qemu/config-file.h" +#include "exec/address-spaces.h" + +/* Kernel boot protocol is specified in the kernel docs + * Documentation/arm/Booting and Documentation/arm64/booting.txt + * They have different preferred image load offsets from system RAM base. + */ +#define KERNEL_ARGS_ADDR 0x100 +#define KERNEL_LOAD_ADDR 0x00010000 +#define KERNEL64_LOAD_ADDR 0x00080000 + +typedef enum { + FIXUP_NONE = 0, /* do nothing */ + FIXUP_TERMINATOR, /* end of insns */ + FIXUP_BOARDID, /* overwrite with board ID number */ + FIXUP_ARGPTR, /* overwrite with pointer to kernel args */ + FIXUP_ENTRYPOINT, /* overwrite with kernel entry point */ + FIXUP_GIC_CPU_IF, /* overwrite with GIC CPU interface address */ + FIXUP_BOOTREG, /* overwrite with boot register address */ + FIXUP_DSB, /* overwrite with correct DSB insn for cpu */ + FIXUP_MAX, +} FixupType; + +typedef struct ARMInsnFixup { + uint32_t insn; + FixupType fixup; +} ARMInsnFixup; + +static const ARMInsnFixup bootloader_aarch64[] = { + { 0x580000c0 }, /* ldr x0, arg ; Load the lower 32-bits of DTB */ + { 0xaa1f03e1 }, /* mov x1, xzr */ + { 0xaa1f03e2 }, /* mov x2, xzr */ + { 0xaa1f03e3 }, /* mov x3, xzr */ + { 0x58000084 }, /* ldr x4, entry ; Load the lower 32-bits of kernel entry */ + { 0xd61f0080 }, /* br x4 ; Jump to the kernel entry point */ + { 0, FIXUP_ARGPTR }, /* arg: .word @DTB Lower 32-bits */ + { 0 }, /* .word @DTB Higher 32-bits */ + { 0, FIXUP_ENTRYPOINT }, /* entry: .word @Kernel Entry Lower 32-bits */ + { 0 }, /* .word @Kernel Entry Higher 32-bits */ + { 0, FIXUP_TERMINATOR } +}; + +/* The worlds second smallest bootloader. Set r0-r2, then jump to kernel. */ +static const ARMInsnFixup bootloader[] = { + { 0xe3a00000 }, /* mov r0, #0 */ + { 0xe59f1004 }, /* ldr r1, [pc, #4] */ + { 0xe59f2004 }, /* ldr r2, [pc, #4] */ + { 0xe59ff004 }, /* ldr pc, [pc, #4] */ + { 0, FIXUP_BOARDID }, + { 0, FIXUP_ARGPTR }, + { 0, FIXUP_ENTRYPOINT }, + { 0, FIXUP_TERMINATOR } +}; + +/* Handling for secondary CPU boot in a multicore system. + * Unlike the uniprocessor/primary CPU boot, this is platform + * dependent. The default code here is based on the secondary + * CPU boot protocol used on realview/vexpress boards, with + * some parameterisation to increase its flexibility. + * QEMU platform models for which this code is not appropriate + * should override write_secondary_boot and secondary_cpu_reset_hook + * instead. + * + * This code enables the interrupt controllers for the secondary + * CPUs and then puts all the secondary CPUs into a loop waiting + * for an interprocessor interrupt and polling a configurable + * location for the kernel secondary CPU entry point. + */ +#define DSB_INSN 0xf57ff04f +#define CP15_DSB_INSN 0xee070f9a /* mcr cp15, 0, r0, c7, c10, 4 */ + +static const ARMInsnFixup smpboot[] = { + { 0xe59f2028 }, /* ldr r2, gic_cpu_if */ + { 0xe59f0028 }, /* ldr r0, bootreg_addr */ + { 0xe3a01001 }, /* mov r1, #1 */ + { 0xe5821000 }, /* str r1, [r2] - set GICC_CTLR.Enable */ + { 0xe3a010ff }, /* mov r1, #0xff */ + { 0xe5821004 }, /* str r1, [r2, 4] - set GIC_PMR.Priority to 0xff */ + { 0, FIXUP_DSB }, /* dsb */ + { 0xe320f003 }, /* wfi */ + { 0xe5901000 }, /* ldr r1, [r0] */ + { 0xe1110001 }, /* tst r1, r1 */ + { 0x0afffffb }, /* beq <wfi> */ + { 0xe12fff11 }, /* bx r1 */ + { 0, FIXUP_GIC_CPU_IF }, /* gic_cpu_if: .word 0x.... */ + { 0, FIXUP_BOOTREG }, /* bootreg_addr: .word 0x.... */ + { 0, FIXUP_TERMINATOR } +}; + +static void write_bootloader(const char *name, hwaddr addr, + const ARMInsnFixup *insns, uint32_t *fixupcontext) +{ + /* Fix up the specified bootloader fragment and write it into + * guest memory using rom_add_blob_fixed(). fixupcontext is + * an array giving the values to write in for the fixup types + * which write a value into the code array. + */ + int i, len; + uint32_t *code; + + len = 0; + while (insns[len].fixup != FIXUP_TERMINATOR) { + len++; + } + + code = g_new0(uint32_t, len); + + for (i = 0; i < len; i++) { + uint32_t insn = insns[i].insn; + FixupType fixup = insns[i].fixup; + + switch (fixup) { + case FIXUP_NONE: + break; + case FIXUP_BOARDID: + case FIXUP_ARGPTR: + case FIXUP_ENTRYPOINT: + case FIXUP_GIC_CPU_IF: + case FIXUP_BOOTREG: + case FIXUP_DSB: + insn = fixupcontext[fixup]; + break; + default: + abort(); + } + code[i] = tswap32(insn); + } + + rom_add_blob_fixed(name, code, len * sizeof(uint32_t), addr); + + g_free(code); +} + +static void default_write_secondary(ARMCPU *cpu, + const struct arm_boot_info *info) +{ + uint32_t fixupcontext[FIXUP_MAX]; + + fixupcontext[FIXUP_GIC_CPU_IF] = info->gic_cpu_if_addr; + fixupcontext[FIXUP_BOOTREG] = info->smp_bootreg_addr; + if (arm_feature(&cpu->env, ARM_FEATURE_V7)) { + fixupcontext[FIXUP_DSB] = DSB_INSN; + } else { + fixupcontext[FIXUP_DSB] = CP15_DSB_INSN; + } + + write_bootloader("smpboot", info->smp_loader_start, + smpboot, fixupcontext); +} + +static void default_reset_secondary(ARMCPU *cpu, + const struct arm_boot_info *info) +{ + CPUState *cs = CPU(cpu); + + address_space_stl_notdirty(&address_space_memory, info->smp_bootreg_addr, + 0, MEMTXATTRS_UNSPECIFIED, NULL); + cpu_set_pc(cs, info->smp_loader_start); +} + +static inline bool have_dtb(const struct arm_boot_info *info) +{ + return info->dtb_filename || info->get_dtb; +} + +#define WRITE_WORD(p, value) do { \ + address_space_stl_notdirty(&address_space_memory, p, value, \ + MEMTXATTRS_UNSPECIFIED, NULL); \ + p += 4; \ +} while (0) + +static void set_kernel_args(const struct arm_boot_info *info) +{ + int initrd_size = info->initrd_size; + hwaddr base = info->loader_start; + hwaddr p; + + p = base + KERNEL_ARGS_ADDR; + /* ATAG_CORE */ + WRITE_WORD(p, 5); + WRITE_WORD(p, 0x54410001); + WRITE_WORD(p, 1); + WRITE_WORD(p, 0x1000); + WRITE_WORD(p, 0); + /* ATAG_MEM */ + /* TODO: handle multiple chips on one ATAG list */ + WRITE_WORD(p, 4); + WRITE_WORD(p, 0x54410002); + WRITE_WORD(p, info->ram_size); + WRITE_WORD(p, info->loader_start); + if (initrd_size) { + /* ATAG_INITRD2 */ + WRITE_WORD(p, 4); + WRITE_WORD(p, 0x54420005); + WRITE_WORD(p, info->initrd_start); + WRITE_WORD(p, initrd_size); + } + if (info->kernel_cmdline && *info->kernel_cmdline) { + /* ATAG_CMDLINE */ + int cmdline_size; + + cmdline_size = strlen(info->kernel_cmdline); + cpu_physical_memory_write(p + 8, info->kernel_cmdline, + cmdline_size + 1); + cmdline_size = (cmdline_size >> 2) + 1; + WRITE_WORD(p, cmdline_size + 2); + WRITE_WORD(p, 0x54410009); + p += cmdline_size * 4; + } + if (info->atag_board) { + /* ATAG_BOARD */ + int atag_board_len; + uint8_t atag_board_buf[0x1000]; + + atag_board_len = (info->atag_board(info, atag_board_buf) + 3) & ~3; + WRITE_WORD(p, (atag_board_len + 8) >> 2); + WRITE_WORD(p, 0x414f4d50); + cpu_physical_memory_write(p, atag_board_buf, atag_board_len); + p += atag_board_len; + } + /* ATAG_END */ + WRITE_WORD(p, 0); + WRITE_WORD(p, 0); +} + +static void set_kernel_args_old(const struct arm_boot_info *info) +{ + hwaddr p; + const char *s; + int initrd_size = info->initrd_size; + hwaddr base = info->loader_start; + + /* see linux/include/asm-arm/setup.h */ + p = base + KERNEL_ARGS_ADDR; + /* page_size */ + WRITE_WORD(p, 4096); + /* nr_pages */ + WRITE_WORD(p, info->ram_size / 4096); + /* ramdisk_size */ + WRITE_WORD(p, 0); +#define FLAG_READONLY 1 +#define FLAG_RDLOAD 4 +#define FLAG_RDPROMPT 8 + /* flags */ + WRITE_WORD(p, FLAG_READONLY | FLAG_RDLOAD | FLAG_RDPROMPT); + /* rootdev */ + WRITE_WORD(p, (31 << 8) | 0); /* /dev/mtdblock0 */ + /* video_num_cols */ + WRITE_WORD(p, 0); + /* video_num_rows */ + WRITE_WORD(p, 0); + /* video_x */ + WRITE_WORD(p, 0); + /* video_y */ + WRITE_WORD(p, 0); + /* memc_control_reg */ + WRITE_WORD(p, 0); + /* unsigned char sounddefault */ + /* unsigned char adfsdrives */ + /* unsigned char bytes_per_char_h */ + /* unsigned char bytes_per_char_v */ + WRITE_WORD(p, 0); + /* pages_in_bank[4] */ + WRITE_WORD(p, 0); + WRITE_WORD(p, 0); + WRITE_WORD(p, 0); + WRITE_WORD(p, 0); + /* pages_in_vram */ + WRITE_WORD(p, 0); + /* initrd_start */ + if (initrd_size) { + WRITE_WORD(p, info->initrd_start); + } else { + WRITE_WORD(p, 0); + } + /* initrd_size */ + WRITE_WORD(p, initrd_size); + /* rd_start */ + WRITE_WORD(p, 0); + /* system_rev */ + WRITE_WORD(p, 0); + /* system_serial_low */ + WRITE_WORD(p, 0); + /* system_serial_high */ + WRITE_WORD(p, 0); + /* mem_fclk_21285 */ + WRITE_WORD(p, 0); + /* zero unused fields */ + while (p < base + KERNEL_ARGS_ADDR + 256 + 1024) { + WRITE_WORD(p, 0); + } + s = info->kernel_cmdline; + if (s) { + cpu_physical_memory_write(p, s, strlen(s) + 1); + } else { + WRITE_WORD(p, 0); + } +} + +/** + * load_dtb() - load a device tree binary image into memory + * @addr: the address to load the image at + * @binfo: struct describing the boot environment + * @addr_limit: upper limit of the available memory area at @addr + * + * Load a device tree supplied by the machine or by the user with the + * '-dtb' command line option, and put it at offset @addr in target + * memory. + * + * If @addr_limit contains a meaningful value (i.e., it is strictly greater + * than @addr), the device tree is only loaded if its size does not exceed + * the limit. + * + * Returns: the size of the device tree image on success, + * 0 if the image size exceeds the limit, + * -1 on errors. + * + * Note: Must not be called unless have_dtb(binfo) is true. + */ +static int load_dtb(hwaddr addr, const struct arm_boot_info *binfo, + hwaddr addr_limit) +{ + void *fdt = NULL; + int size, rc; + uint32_t acells, scells; + + if (binfo->dtb_filename) { + char *filename; + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, binfo->dtb_filename); + if (!filename) { + fprintf(stderr, "Couldn't open dtb file %s\n", binfo->dtb_filename); + goto fail; + } + + fdt = load_device_tree(filename, &size); + if (!fdt) { + fprintf(stderr, "Couldn't open dtb file %s\n", filename); + g_free(filename); + goto fail; + } + g_free(filename); + } else { + fdt = binfo->get_dtb(binfo, &size); + if (!fdt) { + fprintf(stderr, "Board was unable to create a dtb blob\n"); + goto fail; + } + } + + if (addr_limit > addr && size > (addr_limit - addr)) { + /* Installing the device tree blob at addr would exceed addr_limit. + * Whether this constitutes failure is up to the caller to decide, + * so just return 0 as size, i.e., no error. + */ + g_free(fdt); + return 0; + } + + acells = qemu_fdt_getprop_cell(fdt, "/", "#address-cells"); + scells = qemu_fdt_getprop_cell(fdt, "/", "#size-cells"); + if (acells == 0 || scells == 0) { + fprintf(stderr, "dtb file invalid (#address-cells or #size-cells 0)\n"); + goto fail; + } + + if (scells < 2 && binfo->ram_size >= (1ULL << 32)) { + /* This is user error so deserves a friendlier error message + * than the failure of setprop_sized_cells would provide + */ + fprintf(stderr, "qemu: dtb file not compatible with " + "RAM size > 4GB\n"); + goto fail; + } + + rc = qemu_fdt_setprop_sized_cells(fdt, "/memory", "reg", + acells, binfo->loader_start, + scells, binfo->ram_size); + if (rc < 0) { + fprintf(stderr, "couldn't set /memory/reg\n"); + goto fail; + } + + if (binfo->kernel_cmdline && *binfo->kernel_cmdline) { + rc = qemu_fdt_setprop_string(fdt, "/chosen", "bootargs", + binfo->kernel_cmdline); + if (rc < 0) { + fprintf(stderr, "couldn't set /chosen/bootargs\n"); + goto fail; + } + } + + if (binfo->initrd_size) { + rc = qemu_fdt_setprop_cell(fdt, "/chosen", "linux,initrd-start", + binfo->initrd_start); + if (rc < 0) { + fprintf(stderr, "couldn't set /chosen/linux,initrd-start\n"); + goto fail; + } + + rc = qemu_fdt_setprop_cell(fdt, "/chosen", "linux,initrd-end", + binfo->initrd_start + binfo->initrd_size); + if (rc < 0) { + fprintf(stderr, "couldn't set /chosen/linux,initrd-end\n"); + goto fail; + } + } + + if (binfo->modify_dtb) { + binfo->modify_dtb(binfo, fdt); + } + + qemu_fdt_dumpdtb(fdt, size); + + /* Put the DTB into the memory map as a ROM image: this will ensure + * the DTB is copied again upon reset, even if addr points into RAM. + */ + rom_add_blob_fixed("dtb", fdt, size, addr); + + g_free(fdt); + + return size; + +fail: + g_free(fdt); + return -1; +} + +static void do_cpu_reset(void *opaque) +{ + ARMCPU *cpu = opaque; + CPUState *cs = CPU(cpu); + CPUARMState *env = &cpu->env; + const struct arm_boot_info *info = env->boot_info; + + cpu_reset(cs); + if (info) { + if (!info->is_linux) { + /* Jump to the entry point. */ + uint64_t entry = info->entry; + + if (!env->aarch64) { + env->thumb = info->entry & 1; + entry &= 0xfffffffe; + } + cpu_set_pc(cs, entry); + } else { + /* If we are booting Linux then we need to check whether we are + * booting into secure or non-secure state and adjust the state + * accordingly. Out of reset, ARM is defined to be in secure state + * (SCR.NS = 0), we change that here if non-secure boot has been + * requested. + */ + if (arm_feature(env, ARM_FEATURE_EL3)) { + /* AArch64 is defined to come out of reset into EL3 if enabled. + * If we are booting Linux then we need to adjust our EL as + * Linux expects us to be in EL2 or EL1. AArch32 resets into + * SVC, which Linux expects, so no privilege/exception level to + * adjust. + */ + if (env->aarch64) { + if (arm_feature(env, ARM_FEATURE_EL2)) { + env->pstate = PSTATE_MODE_EL2h; + } else { + env->pstate = PSTATE_MODE_EL1h; + } + } + + /* Set to non-secure if not a secure boot */ + if (!info->secure_boot) { + /* Linux expects non-secure state */ + env->cp15.scr_el3 |= SCR_NS; + } + } + + if (cs == first_cpu) { + cpu_set_pc(cs, info->loader_start); + + if (!have_dtb(info)) { + if (old_param) { + set_kernel_args_old(info); + } else { + set_kernel_args(info); + } + } + } else { + info->secondary_cpu_reset_hook(cpu, info); + } + } + } +} + +/** + * load_image_to_fw_cfg() - Load an image file into an fw_cfg entry identified + * by key. + * @fw_cfg: The firmware config instance to store the data in. + * @size_key: The firmware config key to store the size of the loaded + * data under, with fw_cfg_add_i32(). + * @data_key: The firmware config key to store the loaded data under, + * with fw_cfg_add_bytes(). + * @image_name: The name of the image file to load. If it is NULL, the + * function returns without doing anything. + * @try_decompress: Whether the image should be decompressed (gunzipped) before + * adding it to fw_cfg. If decompression fails, the image is + * loaded as-is. + * + * In case of failure, the function prints an error message to stderr and the + * process exits with status 1. + */ +static void load_image_to_fw_cfg(FWCfgState *fw_cfg, uint16_t size_key, + uint16_t data_key, const char *image_name, + bool try_decompress) +{ + size_t size = -1; + uint8_t *data; + + if (image_name == NULL) { + return; + } + + if (try_decompress) { + size = load_image_gzipped_buffer(image_name, + LOAD_IMAGE_MAX_GUNZIP_BYTES, &data); + } + + if (size == (size_t)-1) { + gchar *contents; + gsize length; + + if (!g_file_get_contents(image_name, &contents, &length, NULL)) { + fprintf(stderr, "failed to load \"%s\"\n", image_name); + exit(1); + } + size = length; + data = (uint8_t *)contents; + } + + fw_cfg_add_i32(fw_cfg, size_key, size); + fw_cfg_add_bytes(fw_cfg, data_key, data, size); +} + +static void arm_load_kernel_notify(Notifier *notifier, void *data) +{ + CPUState *cs; + int kernel_size; + int initrd_size; + int is_linux = 0; + uint64_t elf_entry, elf_low_addr, elf_high_addr; + int elf_machine; + hwaddr entry, kernel_load_offset; + int big_endian; + static const ARMInsnFixup *primary_loader; + ArmLoadKernelNotifier *n = DO_UPCAST(ArmLoadKernelNotifier, + notifier, notifier); + ARMCPU *cpu = n->cpu; + struct arm_boot_info *info = + container_of(n, struct arm_boot_info, load_kernel_notifier); + + /* Load the kernel. */ + if (!info->kernel_filename || info->firmware_loaded) { + + if (have_dtb(info)) { + /* If we have a device tree blob, but no kernel to supply it to (or + * the kernel is supposed to be loaded by the bootloader), copy the + * DTB to the base of RAM for the bootloader to pick up. + */ + if (load_dtb(info->loader_start, info, 0) < 0) { + exit(1); + } + } + + if (info->kernel_filename) { + FWCfgState *fw_cfg; + bool try_decompressing_kernel; + + fw_cfg = fw_cfg_find(); + try_decompressing_kernel = arm_feature(&cpu->env, + ARM_FEATURE_AARCH64); + + /* Expose the kernel, the command line, and the initrd in fw_cfg. + * We don't process them here at all, it's all left to the + * firmware. + */ + load_image_to_fw_cfg(fw_cfg, + FW_CFG_KERNEL_SIZE, FW_CFG_KERNEL_DATA, + info->kernel_filename, + try_decompressing_kernel); + load_image_to_fw_cfg(fw_cfg, + FW_CFG_INITRD_SIZE, FW_CFG_INITRD_DATA, + info->initrd_filename, false); + + if (info->kernel_cmdline) { + fw_cfg_add_i32(fw_cfg, FW_CFG_CMDLINE_SIZE, + strlen(info->kernel_cmdline) + 1); + fw_cfg_add_string(fw_cfg, FW_CFG_CMDLINE_DATA, + info->kernel_cmdline); + } + } + + /* We will start from address 0 (typically a boot ROM image) in the + * same way as hardware. + */ + return; + } + + if (arm_feature(&cpu->env, ARM_FEATURE_AARCH64)) { + primary_loader = bootloader_aarch64; + kernel_load_offset = KERNEL64_LOAD_ADDR; + elf_machine = EM_AARCH64; + } else { + primary_loader = bootloader; + kernel_load_offset = KERNEL_LOAD_ADDR; + elf_machine = EM_ARM; + } + + info->dtb_filename = qemu_opt_get(qemu_get_machine_opts(), "dtb"); + + if (!info->secondary_cpu_reset_hook) { + info->secondary_cpu_reset_hook = default_reset_secondary; + } + if (!info->write_secondary_boot) { + info->write_secondary_boot = default_write_secondary; + } + + if (info->nb_cpus == 0) + info->nb_cpus = 1; + +#ifdef TARGET_WORDS_BIGENDIAN + big_endian = 1; +#else + big_endian = 0; +#endif + + /* We want to put the initrd far enough into RAM that when the + * kernel is uncompressed it will not clobber the initrd. However + * on boards without much RAM we must ensure that we still leave + * enough room for a decent sized initrd, and on boards with large + * amounts of RAM we must avoid the initrd being so far up in RAM + * that it is outside lowmem and inaccessible to the kernel. + * So for boards with less than 256MB of RAM we put the initrd + * halfway into RAM, and for boards with 256MB of RAM or more we put + * the initrd at 128MB. + */ + info->initrd_start = info->loader_start + + MIN(info->ram_size / 2, 128 * 1024 * 1024); + + /* Assume that raw images are linux kernels, and ELF images are not. */ + kernel_size = load_elf(info->kernel_filename, NULL, NULL, &elf_entry, + &elf_low_addr, &elf_high_addr, big_endian, + elf_machine, 1); + if (kernel_size > 0 && have_dtb(info)) { + /* If there is still some room left at the base of RAM, try and put + * the DTB there like we do for images loaded with -bios or -pflash. + */ + if (elf_low_addr > info->loader_start + || elf_high_addr < info->loader_start) { + /* Pass elf_low_addr as address limit to load_dtb if it may be + * pointing into RAM, otherwise pass '0' (no limit) + */ + if (elf_low_addr < info->loader_start) { + elf_low_addr = 0; + } + if (load_dtb(info->loader_start, info, elf_low_addr) < 0) { + exit(1); + } + } + } + entry = elf_entry; + if (kernel_size < 0) { + kernel_size = load_uimage(info->kernel_filename, &entry, NULL, + &is_linux, NULL, NULL); + } + /* On aarch64, it's the bootloader's job to uncompress the kernel. */ + if (arm_feature(&cpu->env, ARM_FEATURE_AARCH64) && kernel_size < 0) { + entry = info->loader_start + kernel_load_offset; + kernel_size = load_image_gzipped(info->kernel_filename, entry, + info->ram_size - kernel_load_offset); + is_linux = 1; + } + if (kernel_size < 0) { + entry = info->loader_start + kernel_load_offset; + kernel_size = load_image_targphys(info->kernel_filename, entry, + info->ram_size - kernel_load_offset); + is_linux = 1; + } + if (kernel_size < 0) { + fprintf(stderr, "qemu: could not load kernel '%s'\n", + info->kernel_filename); + exit(1); + } + info->entry = entry; + if (is_linux) { + uint32_t fixupcontext[FIXUP_MAX]; + + if (info->initrd_filename) { + initrd_size = load_ramdisk(info->initrd_filename, + info->initrd_start, + info->ram_size - + info->initrd_start); + if (initrd_size < 0) { + initrd_size = load_image_targphys(info->initrd_filename, + info->initrd_start, + info->ram_size - + info->initrd_start); + } + if (initrd_size < 0) { + fprintf(stderr, "qemu: could not load initrd '%s'\n", + info->initrd_filename); + exit(1); + } + } else { + initrd_size = 0; + } + info->initrd_size = initrd_size; + + fixupcontext[FIXUP_BOARDID] = info->board_id; + + /* for device tree boot, we pass the DTB directly in r2. Otherwise + * we point to the kernel args. + */ + if (have_dtb(info)) { + hwaddr align; + hwaddr dtb_start; + + if (elf_machine == EM_AARCH64) { + /* + * Some AArch64 kernels on early bootup map the fdt region as + * + * [ ALIGN_DOWN(fdt, 2MB) ... ALIGN_DOWN(fdt, 2MB) + 2MB ] + * + * Let's play safe and prealign it to 2MB to give us some space. + */ + align = 2 * 1024 * 1024; + } else { + /* + * Some 32bit kernels will trash anything in the 4K page the + * initrd ends in, so make sure the DTB isn't caught up in that. + */ + align = 4096; + } + + /* Place the DTB after the initrd in memory with alignment. */ + dtb_start = QEMU_ALIGN_UP(info->initrd_start + initrd_size, align); + if (load_dtb(dtb_start, info, 0) < 0) { + exit(1); + } + fixupcontext[FIXUP_ARGPTR] = dtb_start; + } else { + fixupcontext[FIXUP_ARGPTR] = info->loader_start + KERNEL_ARGS_ADDR; + if (info->ram_size >= (1ULL << 32)) { + fprintf(stderr, "qemu: RAM size must be less than 4GB to boot" + " Linux kernel using ATAGS (try passing a device tree" + " using -dtb)\n"); + exit(1); + } + } + fixupcontext[FIXUP_ENTRYPOINT] = entry; + + write_bootloader("bootloader", info->loader_start, + primary_loader, fixupcontext); + + if (info->nb_cpus > 1) { + info->write_secondary_boot(cpu, info); + } + } + info->is_linux = is_linux; + + for (cs = CPU(cpu); cs; cs = CPU_NEXT(cs)) { + ARM_CPU(cs)->env.boot_info = info; + } +} + +void arm_load_kernel(ARMCPU *cpu, struct arm_boot_info *info) +{ + CPUState *cs; + + info->load_kernel_notifier.cpu = cpu; + info->load_kernel_notifier.notifier.notify = arm_load_kernel_notify; + qemu_add_machine_init_done_notifier(&info->load_kernel_notifier.notifier); + + /* CPU objects (unlike devices) are not automatically reset on system + * reset, so we must always register a handler to do so. If we're + * actually loading a kernel, the handler is also responsible for + * arranging that we start it correctly. + */ + for (cs = CPU(cpu); cs; cs = CPU_NEXT(cs)) { + qemu_register_reset(do_cpu_reset, ARM_CPU(cs)); + } +} diff --git a/qemu/hw/arm/collie.c b/qemu/hw/arm/collie.c new file mode 100644 index 000000000..6c9b82fc5 --- /dev/null +++ b/qemu/hw/arm/collie.c @@ -0,0 +1,72 @@ +/* + * SA-1110-based Sharp Zaurus SL-5500 platform. + * + * Copyright (C) 2011 Dmitry Eremin-Solenikov + * + * This code is licensed under GNU GPL v2. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ +#include "hw/hw.h" +#include "hw/sysbus.h" +#include "hw/boards.h" +#include "hw/devices.h" +#include "strongarm.h" +#include "hw/arm/arm.h" +#include "hw/block/flash.h" +#include "sysemu/block-backend.h" +#include "exec/address-spaces.h" + +static struct arm_boot_info collie_binfo = { + .loader_start = SA_SDCS0, + .ram_size = 0x20000000, +}; + +static void collie_init(MachineState *machine) +{ + const char *cpu_model = machine->cpu_model; + const char *kernel_filename = machine->kernel_filename; + const char *kernel_cmdline = machine->kernel_cmdline; + const char *initrd_filename = machine->initrd_filename; + StrongARMState *s; + DriveInfo *dinfo; + MemoryRegion *sysmem = get_system_memory(); + + if (!cpu_model) { + cpu_model = "sa1110"; + } + + s = sa1110_init(sysmem, collie_binfo.ram_size, cpu_model); + + dinfo = drive_get(IF_PFLASH, 0, 0); + pflash_cfi01_register(SA_CS0, NULL, "collie.fl1", 0x02000000, + dinfo ? blk_by_legacy_dinfo(dinfo) : NULL, + (64 * 1024), 512, 4, 0x00, 0x00, 0x00, 0x00, 0); + + dinfo = drive_get(IF_PFLASH, 0, 1); + pflash_cfi01_register(SA_CS1, NULL, "collie.fl2", 0x02000000, + dinfo ? blk_by_legacy_dinfo(dinfo) : NULL, + (64 * 1024), 512, 4, 0x00, 0x00, 0x00, 0x00, 0); + + sysbus_create_simple("scoop", 0x40800000, NULL); + + collie_binfo.kernel_filename = kernel_filename; + collie_binfo.kernel_cmdline = kernel_cmdline; + collie_binfo.initrd_filename = initrd_filename; + collie_binfo.board_id = 0x208; + arm_load_kernel(s->cpu, &collie_binfo); +} + +static QEMUMachine collie_machine = { + .name = "collie", + .desc = "Collie PDA (SA-1110)", + .init = collie_init, +}; + +static void collie_machine_init(void) +{ + qemu_register_machine(&collie_machine); +} + +machine_init(collie_machine_init) diff --git a/qemu/hw/arm/cubieboard.c b/qemu/hw/arm/cubieboard.c new file mode 100644 index 000000000..1582250eb --- /dev/null +++ b/qemu/hw/arm/cubieboard.c @@ -0,0 +1,89 @@ +/* + * cubieboard emulation + * + * Copyright (C) 2013 Li Guang + * Written by Li Guang <lig.fnst@cn.fujitsu.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "hw/sysbus.h" +#include "hw/devices.h" +#include "hw/boards.h" +#include "hw/arm/allwinner-a10.h" + +static struct arm_boot_info cubieboard_binfo = { + .loader_start = AW_A10_SDRAM_BASE, + .board_id = 0x1008, +}; + +typedef struct CubieBoardState { + AwA10State *a10; + MemoryRegion sdram; +} CubieBoardState; + +static void cubieboard_init(MachineState *machine) +{ + CubieBoardState *s = g_new(CubieBoardState, 1); + Error *err = NULL; + + s->a10 = AW_A10(object_new(TYPE_AW_A10)); + + object_property_set_int(OBJECT(&s->a10->emac), 1, "phy-addr", &err); + if (err != NULL) { + error_report("Couldn't set phy address: %s", error_get_pretty(err)); + exit(1); + } + + object_property_set_int(OBJECT(&s->a10->timer), 32768, "clk0-freq", &err); + if (err != NULL) { + error_report("Couldn't set clk0 frequency: %s", error_get_pretty(err)); + exit(1); + } + + object_property_set_int(OBJECT(&s->a10->timer), 24000000, "clk1-freq", + &err); + if (err != NULL) { + error_report("Couldn't set clk1 frequency: %s", error_get_pretty(err)); + exit(1); + } + + object_property_set_bool(OBJECT(s->a10), true, "realized", &err); + if (err != NULL) { + error_report("Couldn't realize Allwinner A10: %s", + error_get_pretty(err)); + exit(1); + } + + memory_region_allocate_system_memory(&s->sdram, NULL, "cubieboard.ram", + machine->ram_size); + memory_region_add_subregion(get_system_memory(), AW_A10_SDRAM_BASE, + &s->sdram); + + cubieboard_binfo.ram_size = machine->ram_size; + cubieboard_binfo.kernel_filename = machine->kernel_filename; + cubieboard_binfo.kernel_cmdline = machine->kernel_cmdline; + arm_load_kernel(&s->a10->cpu, &cubieboard_binfo); +} + +static QEMUMachine cubieboard_machine = { + .name = "cubieboard", + .desc = "cubietech cubieboard", + .init = cubieboard_init, +}; + + +static void cubieboard_machine_init(void) +{ + qemu_register_machine(&cubieboard_machine); +} + +machine_init(cubieboard_machine_init) diff --git a/qemu/hw/arm/digic.c b/qemu/hw/arm/digic.c new file mode 100644 index 000000000..ec8c33060 --- /dev/null +++ b/qemu/hw/arm/digic.c @@ -0,0 +1,115 @@ +/* + * QEMU model of the Canon DIGIC SoC. + * + * Copyright (C) 2013 Antony Pavlov <antonynpavlov@gmail.com> + * + * This model is based on reverse engineering efforts + * made by CHDK (http://chdk.wikia.com) and + * Magic Lantern (http://www.magiclantern.fm) projects + * contributors. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "hw/arm/digic.h" + +#define DIGIC4_TIMER_BASE(n) (0xc0210000 + (n) * 0x100) + +#define DIGIC_UART_BASE 0xc0800000 + +static void digic_init(Object *obj) +{ + DigicState *s = DIGIC(obj); + DeviceState *dev; + int i; + + object_initialize(&s->cpu, sizeof(s->cpu), "arm946-" TYPE_ARM_CPU); + object_property_add_child(obj, "cpu", OBJECT(&s->cpu), NULL); + + for (i = 0; i < DIGIC4_NB_TIMERS; i++) { +#define DIGIC_TIMER_NAME_MLEN 11 + char name[DIGIC_TIMER_NAME_MLEN]; + + object_initialize(&s->timer[i], sizeof(s->timer[i]), TYPE_DIGIC_TIMER); + dev = DEVICE(&s->timer[i]); + qdev_set_parent_bus(dev, sysbus_get_default()); + snprintf(name, DIGIC_TIMER_NAME_MLEN, "timer[%d]", i); + object_property_add_child(obj, name, OBJECT(&s->timer[i]), NULL); + } + + object_initialize(&s->uart, sizeof(s->uart), TYPE_DIGIC_UART); + dev = DEVICE(&s->uart); + qdev_set_parent_bus(dev, sysbus_get_default()); + object_property_add_child(obj, "uart", OBJECT(&s->uart), NULL); +} + +static void digic_realize(DeviceState *dev, Error **errp) +{ + DigicState *s = DIGIC(dev); + Error *err = NULL; + SysBusDevice *sbd; + int i; + + object_property_set_bool(OBJECT(&s->cpu), true, "reset-hivecs", &err); + if (err != NULL) { + error_propagate(errp, err); + return; + } + + object_property_set_bool(OBJECT(&s->cpu), true, "realized", &err); + if (err != NULL) { + error_propagate(errp, err); + return; + } + + for (i = 0; i < DIGIC4_NB_TIMERS; i++) { + object_property_set_bool(OBJECT(&s->timer[i]), true, "realized", &err); + if (err != NULL) { + error_propagate(errp, err); + return; + } + + sbd = SYS_BUS_DEVICE(&s->timer[i]); + sysbus_mmio_map(sbd, 0, DIGIC4_TIMER_BASE(i)); + } + + object_property_set_bool(OBJECT(&s->uart), true, "realized", &err); + if (err != NULL) { + error_propagate(errp, err); + return; + } + + sbd = SYS_BUS_DEVICE(&s->uart); + sysbus_mmio_map(sbd, 0, DIGIC_UART_BASE); +} + +static void digic_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = digic_realize; +} + +static const TypeInfo digic_type_info = { + .name = TYPE_DIGIC, + .parent = TYPE_DEVICE, + .instance_size = sizeof(DigicState), + .instance_init = digic_init, + .class_init = digic_class_init, +}; + +static void digic_register_types(void) +{ + type_register_static(&digic_type_info); +} + +type_init(digic_register_types) diff --git a/qemu/hw/arm/digic_boards.c b/qemu/hw/arm/digic_boards.c new file mode 100644 index 000000000..f8ba9e595 --- /dev/null +++ b/qemu/hw/arm/digic_boards.c @@ -0,0 +1,162 @@ +/* + * QEMU model of the Canon DIGIC boards (cameras indeed :). + * + * Copyright (C) 2013 Antony Pavlov <antonynpavlov@gmail.com> + * + * This model is based on reverse engineering efforts + * made by CHDK (http://chdk.wikia.com) and + * Magic Lantern (http://www.magiclantern.fm) projects + * contributors. + * + * See docs here: + * http://magiclantern.wikia.com/wiki/Register_Map + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "hw/boards.h" +#include "exec/address-spaces.h" +#include "qemu/error-report.h" +#include "hw/arm/digic.h" +#include "hw/block/flash.h" +#include "hw/loader.h" +#include "sysemu/sysemu.h" +#include "sysemu/qtest.h" + +#define DIGIC4_ROM0_BASE 0xf0000000 +#define DIGIC4_ROM1_BASE 0xf8000000 +#define DIGIC4_ROM_MAX_SIZE 0x08000000 + +typedef struct DigicBoardState { + DigicState *digic; + MemoryRegion ram; +} DigicBoardState; + +typedef struct DigicBoard { + hwaddr ram_size; + void (*add_rom0)(DigicBoardState *, hwaddr, const char *); + const char *rom0_def_filename; + void (*add_rom1)(DigicBoardState *, hwaddr, const char *); + const char *rom1_def_filename; +} DigicBoard; + +static void digic4_board_setup_ram(DigicBoardState *s, hwaddr ram_size) +{ + memory_region_allocate_system_memory(&s->ram, NULL, "ram", ram_size); + memory_region_add_subregion(get_system_memory(), 0, &s->ram); +} + +static void digic4_board_init(DigicBoard *board) +{ + Error *err = NULL; + + DigicBoardState *s = g_new(DigicBoardState, 1); + + s->digic = DIGIC(object_new(TYPE_DIGIC)); + object_property_set_bool(OBJECT(s->digic), true, "realized", &err); + if (err != NULL) { + error_report("Couldn't realize DIGIC SoC: %s", + error_get_pretty(err)); + exit(1); + } + + digic4_board_setup_ram(s, board->ram_size); + + if (board->add_rom0) { + board->add_rom0(s, DIGIC4_ROM0_BASE, board->rom0_def_filename); + } + + if (board->add_rom1) { + board->add_rom1(s, DIGIC4_ROM1_BASE, board->rom1_def_filename); + } +} + +static void digic_load_rom(DigicBoardState *s, hwaddr addr, + hwaddr max_size, const char *def_filename) +{ + target_long rom_size; + const char *filename; + + if (qtest_enabled()) { + /* qtest runs no code so don't attempt a ROM load which + * could fail and result in a spurious test failure. + */ + return; + } + + if (bios_name) { + filename = bios_name; + } else { + filename = def_filename; + } + + if (filename) { + char *fn = qemu_find_file(QEMU_FILE_TYPE_BIOS, filename); + + if (!fn) { + error_report("Couldn't find rom image '%s'.", filename); + exit(1); + } + + rom_size = load_image_targphys(fn, addr, max_size); + if (rom_size < 0 || rom_size > max_size) { + error_report("Couldn't load rom image '%s'.", filename); + exit(1); + } + g_free(fn); + } +} + +/* + * Samsung K8P3215UQB + * 64M Bit (4Mx16) Page Mode / Multi-Bank NOR Flash Memory + */ +static void digic4_add_k8p3215uqb_rom(DigicBoardState *s, hwaddr addr, + const char *def_filename) +{ +#define FLASH_K8P3215UQB_SIZE (4 * 1024 * 1024) +#define FLASH_K8P3215UQB_SECTOR_SIZE (64 * 1024) + + pflash_cfi02_register(addr, NULL, "pflash", FLASH_K8P3215UQB_SIZE, + NULL, FLASH_K8P3215UQB_SECTOR_SIZE, + FLASH_K8P3215UQB_SIZE / FLASH_K8P3215UQB_SECTOR_SIZE, + DIGIC4_ROM_MAX_SIZE / FLASH_K8P3215UQB_SIZE, + 4, + 0x00EC, 0x007E, 0x0003, 0x0001, + 0x0555, 0x2aa, 0); + + digic_load_rom(s, addr, FLASH_K8P3215UQB_SIZE, def_filename); +} + +static DigicBoard digic4_board_canon_a1100 = { + .ram_size = 64 * 1024 * 1024, + .add_rom1 = digic4_add_k8p3215uqb_rom, + .rom1_def_filename = "canon-a1100-rom1.bin", +}; + +static void canon_a1100_init(MachineState *machine) +{ + digic4_board_init(&digic4_board_canon_a1100); +} + +static QEMUMachine canon_a1100 = { + .name = "canon-a1100", + .desc = "Canon PowerShot A1100 IS", + .init = &canon_a1100_init, +}; + +static void digic_register_machines(void) +{ + qemu_register_machine(&canon_a1100); +} + +machine_init(digic_register_machines) diff --git a/qemu/hw/arm/exynos4210.c b/qemu/hw/arm/exynos4210.c new file mode 100644 index 000000000..c55fab813 --- /dev/null +++ b/qemu/hw/arm/exynos4210.c @@ -0,0 +1,383 @@ +/* + * Samsung exynos4210 SoC emulation + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. All rights reserved. + * Maksim Kozlov <m.kozlov@samsung.com> + * Evgeny Voevodin <e.voevodin@samsung.com> + * Igor Mitsyanko <i.mitsyanko@samsung.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "hw/boards.h" +#include "sysemu/sysemu.h" +#include "hw/sysbus.h" +#include "hw/arm/arm.h" +#include "hw/loader.h" +#include "hw/arm/exynos4210.h" +#include "hw/usb/hcd-ehci.h" + +#define EXYNOS4210_CHIPID_ADDR 0x10000000 + +/* PWM */ +#define EXYNOS4210_PWM_BASE_ADDR 0x139D0000 + +/* RTC */ +#define EXYNOS4210_RTC_BASE_ADDR 0x10070000 + +/* MCT */ +#define EXYNOS4210_MCT_BASE_ADDR 0x10050000 + +/* I2C */ +#define EXYNOS4210_I2C_SHIFT 0x00010000 +#define EXYNOS4210_I2C_BASE_ADDR 0x13860000 +/* Interrupt Group of External Interrupt Combiner for I2C */ +#define EXYNOS4210_I2C_INTG 27 +#define EXYNOS4210_HDMI_INTG 16 + +/* UART's definitions */ +#define EXYNOS4210_UART0_BASE_ADDR 0x13800000 +#define EXYNOS4210_UART1_BASE_ADDR 0x13810000 +#define EXYNOS4210_UART2_BASE_ADDR 0x13820000 +#define EXYNOS4210_UART3_BASE_ADDR 0x13830000 +#define EXYNOS4210_UART0_FIFO_SIZE 256 +#define EXYNOS4210_UART1_FIFO_SIZE 64 +#define EXYNOS4210_UART2_FIFO_SIZE 16 +#define EXYNOS4210_UART3_FIFO_SIZE 16 +/* Interrupt Group of External Interrupt Combiner for UART */ +#define EXYNOS4210_UART_INT_GRP 26 + +/* External GIC */ +#define EXYNOS4210_EXT_GIC_CPU_BASE_ADDR 0x10480000 +#define EXYNOS4210_EXT_GIC_DIST_BASE_ADDR 0x10490000 + +/* Combiner */ +#define EXYNOS4210_EXT_COMBINER_BASE_ADDR 0x10440000 +#define EXYNOS4210_INT_COMBINER_BASE_ADDR 0x10448000 + +/* PMU SFR base address */ +#define EXYNOS4210_PMU_BASE_ADDR 0x10020000 + +/* Display controllers (FIMD) */ +#define EXYNOS4210_FIMD0_BASE_ADDR 0x11C00000 + +/* EHCI */ +#define EXYNOS4210_EHCI_BASE_ADDR 0x12580000 + +static uint8_t chipid_and_omr[] = { 0x11, 0x02, 0x21, 0x43, + 0x09, 0x00, 0x00, 0x00 }; + +static uint64_t exynos4210_chipid_and_omr_read(void *opaque, hwaddr offset, + unsigned size) +{ + assert(offset < sizeof(chipid_and_omr)); + return chipid_and_omr[offset]; +} + +static void exynos4210_chipid_and_omr_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + return; +} + +static const MemoryRegionOps exynos4210_chipid_and_omr_ops = { + .read = exynos4210_chipid_and_omr_read, + .write = exynos4210_chipid_and_omr_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .impl = { + .max_access_size = 1, + } +}; + +void exynos4210_write_secondary(ARMCPU *cpu, + const struct arm_boot_info *info) +{ + int n; + uint32_t smpboot[] = { + 0xe59f3034, /* ldr r3, External gic_cpu_if */ + 0xe59f2034, /* ldr r2, Internal gic_cpu_if */ + 0xe59f0034, /* ldr r0, startaddr */ + 0xe3a01001, /* mov r1, #1 */ + 0xe5821000, /* str r1, [r2] */ + 0xe5831000, /* str r1, [r3] */ + 0xe3a010ff, /* mov r1, #0xff */ + 0xe5821004, /* str r1, [r2, #4] */ + 0xe5831004, /* str r1, [r3, #4] */ + 0xf57ff04f, /* dsb */ + 0xe320f003, /* wfi */ + 0xe5901000, /* ldr r1, [r0] */ + 0xe1110001, /* tst r1, r1 */ + 0x0afffffb, /* beq <wfi> */ + 0xe12fff11, /* bx r1 */ + EXYNOS4210_EXT_GIC_CPU_BASE_ADDR, + 0, /* gic_cpu_if: base address of Internal GIC CPU interface */ + 0 /* bootreg: Boot register address is held here */ + }; + smpboot[ARRAY_SIZE(smpboot) - 1] = info->smp_bootreg_addr; + smpboot[ARRAY_SIZE(smpboot) - 2] = info->gic_cpu_if_addr; + for (n = 0; n < ARRAY_SIZE(smpboot); n++) { + smpboot[n] = tswap32(smpboot[n]); + } + rom_add_blob_fixed("smpboot", smpboot, sizeof(smpboot), + info->smp_loader_start); +} + +Exynos4210State *exynos4210_init(MemoryRegion *system_mem, + unsigned long ram_size) +{ + int i, n; + Exynos4210State *s = g_new(Exynos4210State, 1); + qemu_irq gate_irq[EXYNOS4210_NCPUS][EXYNOS4210_IRQ_GATE_NINPUTS]; + unsigned long mem_size; + DeviceState *dev; + SysBusDevice *busdev; + ObjectClass *cpu_oc; + + cpu_oc = cpu_class_by_name(TYPE_ARM_CPU, "cortex-a9"); + assert(cpu_oc); + + for (n = 0; n < EXYNOS4210_NCPUS; n++) { + Object *cpuobj = object_new(object_class_get_name(cpu_oc)); + Error *err = NULL; + + /* By default A9 CPUs have EL3 enabled. This board does not currently + * support EL3 so the CPU EL3 property is disabled before realization. + */ + if (object_property_find(cpuobj, "has_el3", NULL)) { + object_property_set_bool(cpuobj, false, "has_el3", &err); + if (err) { + error_report_err(err); + exit(1); + } + } + + s->cpu[n] = ARM_CPU(cpuobj); + object_property_set_int(cpuobj, EXYNOS4210_SMP_PRIVATE_BASE_ADDR, + "reset-cbar", &error_abort); + object_property_set_bool(cpuobj, true, "realized", &err); + if (err) { + error_report_err(err); + exit(1); + } + } + + /*** IRQs ***/ + + s->irq_table = exynos4210_init_irq(&s->irqs); + + /* IRQ Gate */ + for (i = 0; i < EXYNOS4210_NCPUS; i++) { + dev = qdev_create(NULL, "exynos4210.irq_gate"); + qdev_prop_set_uint32(dev, "n_in", EXYNOS4210_IRQ_GATE_NINPUTS); + qdev_init_nofail(dev); + /* Get IRQ Gate input in gate_irq */ + for (n = 0; n < EXYNOS4210_IRQ_GATE_NINPUTS; n++) { + gate_irq[i][n] = qdev_get_gpio_in(dev, n); + } + busdev = SYS_BUS_DEVICE(dev); + + /* Connect IRQ Gate output to CPU's IRQ line */ + sysbus_connect_irq(busdev, 0, + qdev_get_gpio_in(DEVICE(s->cpu[i]), ARM_CPU_IRQ)); + } + + /* Private memory region and Internal GIC */ + dev = qdev_create(NULL, "a9mpcore_priv"); + qdev_prop_set_uint32(dev, "num-cpu", EXYNOS4210_NCPUS); + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(busdev, 0, EXYNOS4210_SMP_PRIVATE_BASE_ADDR); + for (n = 0; n < EXYNOS4210_NCPUS; n++) { + sysbus_connect_irq(busdev, n, gate_irq[n][0]); + } + for (n = 0; n < EXYNOS4210_INT_GIC_NIRQ; n++) { + s->irqs.int_gic_irq[n] = qdev_get_gpio_in(dev, n); + } + + /* Cache controller */ + sysbus_create_simple("l2x0", EXYNOS4210_L2X0_BASE_ADDR, NULL); + + /* External GIC */ + dev = qdev_create(NULL, "exynos4210.gic"); + qdev_prop_set_uint32(dev, "num-cpu", EXYNOS4210_NCPUS); + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + /* Map CPU interface */ + sysbus_mmio_map(busdev, 0, EXYNOS4210_EXT_GIC_CPU_BASE_ADDR); + /* Map Distributer interface */ + sysbus_mmio_map(busdev, 1, EXYNOS4210_EXT_GIC_DIST_BASE_ADDR); + for (n = 0; n < EXYNOS4210_NCPUS; n++) { + sysbus_connect_irq(busdev, n, gate_irq[n][1]); + } + for (n = 0; n < EXYNOS4210_EXT_GIC_NIRQ; n++) { + s->irqs.ext_gic_irq[n] = qdev_get_gpio_in(dev, n); + } + + /* Internal Interrupt Combiner */ + dev = qdev_create(NULL, "exynos4210.combiner"); + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + for (n = 0; n < EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ; n++) { + sysbus_connect_irq(busdev, n, s->irqs.int_gic_irq[n]); + } + exynos4210_combiner_get_gpioin(&s->irqs, dev, 0); + sysbus_mmio_map(busdev, 0, EXYNOS4210_INT_COMBINER_BASE_ADDR); + + /* External Interrupt Combiner */ + dev = qdev_create(NULL, "exynos4210.combiner"); + qdev_prop_set_uint32(dev, "external", 1); + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + for (n = 0; n < EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ; n++) { + sysbus_connect_irq(busdev, n, s->irqs.ext_gic_irq[n]); + } + exynos4210_combiner_get_gpioin(&s->irqs, dev, 1); + sysbus_mmio_map(busdev, 0, EXYNOS4210_EXT_COMBINER_BASE_ADDR); + + /* Initialize board IRQs. */ + exynos4210_init_board_irqs(&s->irqs); + + /*** Memory ***/ + + /* Chip-ID and OMR */ + memory_region_init_io(&s->chipid_mem, NULL, &exynos4210_chipid_and_omr_ops, + NULL, "exynos4210.chipid", sizeof(chipid_and_omr)); + memory_region_add_subregion(system_mem, EXYNOS4210_CHIPID_ADDR, + &s->chipid_mem); + + /* Internal ROM */ + memory_region_init_ram(&s->irom_mem, NULL, "exynos4210.irom", + EXYNOS4210_IROM_SIZE, &error_abort); + vmstate_register_ram_global(&s->irom_mem); + memory_region_set_readonly(&s->irom_mem, true); + memory_region_add_subregion(system_mem, EXYNOS4210_IROM_BASE_ADDR, + &s->irom_mem); + /* mirror of iROM */ + memory_region_init_alias(&s->irom_alias_mem, NULL, "exynos4210.irom_alias", + &s->irom_mem, + 0, + EXYNOS4210_IROM_SIZE); + memory_region_set_readonly(&s->irom_alias_mem, true); + memory_region_add_subregion(system_mem, EXYNOS4210_IROM_MIRROR_BASE_ADDR, + &s->irom_alias_mem); + + /* Internal RAM */ + memory_region_init_ram(&s->iram_mem, NULL, "exynos4210.iram", + EXYNOS4210_IRAM_SIZE, &error_abort); + vmstate_register_ram_global(&s->iram_mem); + memory_region_add_subregion(system_mem, EXYNOS4210_IRAM_BASE_ADDR, + &s->iram_mem); + + /* DRAM */ + mem_size = ram_size; + if (mem_size > EXYNOS4210_DRAM_MAX_SIZE) { + memory_region_init_ram(&s->dram1_mem, NULL, "exynos4210.dram1", + mem_size - EXYNOS4210_DRAM_MAX_SIZE, &error_abort); + vmstate_register_ram_global(&s->dram1_mem); + memory_region_add_subregion(system_mem, EXYNOS4210_DRAM1_BASE_ADDR, + &s->dram1_mem); + mem_size = EXYNOS4210_DRAM_MAX_SIZE; + } + memory_region_init_ram(&s->dram0_mem, NULL, "exynos4210.dram0", mem_size, + &error_abort); + vmstate_register_ram_global(&s->dram0_mem); + memory_region_add_subregion(system_mem, EXYNOS4210_DRAM0_BASE_ADDR, + &s->dram0_mem); + + /* PMU. + * The only reason of existence at the moment is that secondary CPU boot + * loader uses PMU INFORM5 register as a holding pen. + */ + sysbus_create_simple("exynos4210.pmu", EXYNOS4210_PMU_BASE_ADDR, NULL); + + /* PWM */ + sysbus_create_varargs("exynos4210.pwm", EXYNOS4210_PWM_BASE_ADDR, + s->irq_table[exynos4210_get_irq(22, 0)], + s->irq_table[exynos4210_get_irq(22, 1)], + s->irq_table[exynos4210_get_irq(22, 2)], + s->irq_table[exynos4210_get_irq(22, 3)], + s->irq_table[exynos4210_get_irq(22, 4)], + NULL); + /* RTC */ + sysbus_create_varargs("exynos4210.rtc", EXYNOS4210_RTC_BASE_ADDR, + s->irq_table[exynos4210_get_irq(23, 0)], + s->irq_table[exynos4210_get_irq(23, 1)], + NULL); + + /* Multi Core Timer */ + dev = qdev_create(NULL, "exynos4210.mct"); + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + for (n = 0; n < 4; n++) { + /* Connect global timer interrupts to Combiner gpio_in */ + sysbus_connect_irq(busdev, n, + s->irq_table[exynos4210_get_irq(1, 4 + n)]); + } + /* Connect local timer interrupts to Combiner gpio_in */ + sysbus_connect_irq(busdev, 4, + s->irq_table[exynos4210_get_irq(51, 0)]); + sysbus_connect_irq(busdev, 5, + s->irq_table[exynos4210_get_irq(35, 3)]); + sysbus_mmio_map(busdev, 0, EXYNOS4210_MCT_BASE_ADDR); + + /*** I2C ***/ + for (n = 0; n < EXYNOS4210_I2C_NUMBER; n++) { + uint32_t addr = EXYNOS4210_I2C_BASE_ADDR + EXYNOS4210_I2C_SHIFT * n; + qemu_irq i2c_irq; + + if (n < 8) { + i2c_irq = s->irq_table[exynos4210_get_irq(EXYNOS4210_I2C_INTG, n)]; + } else { + i2c_irq = s->irq_table[exynos4210_get_irq(EXYNOS4210_HDMI_INTG, 1)]; + } + + dev = qdev_create(NULL, "exynos4210.i2c"); + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + sysbus_connect_irq(busdev, 0, i2c_irq); + sysbus_mmio_map(busdev, 0, addr); + s->i2c_if[n] = (I2CBus *)qdev_get_child_bus(dev, "i2c"); + } + + + /*** UARTs ***/ + exynos4210_uart_create(EXYNOS4210_UART0_BASE_ADDR, + EXYNOS4210_UART0_FIFO_SIZE, 0, NULL, + s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 0)]); + + exynos4210_uart_create(EXYNOS4210_UART1_BASE_ADDR, + EXYNOS4210_UART1_FIFO_SIZE, 1, NULL, + s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 1)]); + + exynos4210_uart_create(EXYNOS4210_UART2_BASE_ADDR, + EXYNOS4210_UART2_FIFO_SIZE, 2, NULL, + s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 2)]); + + exynos4210_uart_create(EXYNOS4210_UART3_BASE_ADDR, + EXYNOS4210_UART3_FIFO_SIZE, 3, NULL, + s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 3)]); + + /*** Display controller (FIMD) ***/ + sysbus_create_varargs("exynos4210.fimd", EXYNOS4210_FIMD0_BASE_ADDR, + s->irq_table[exynos4210_get_irq(11, 0)], + s->irq_table[exynos4210_get_irq(11, 1)], + s->irq_table[exynos4210_get_irq(11, 2)], + NULL); + + sysbus_create_simple(TYPE_EXYNOS4210_EHCI, EXYNOS4210_EHCI_BASE_ADDR, + s->irq_table[exynos4210_get_irq(28, 3)]); + + return s; +} diff --git a/qemu/hw/arm/exynos4_boards.c b/qemu/hw/arm/exynos4_boards.c new file mode 100644 index 000000000..d644db1ef --- /dev/null +++ b/qemu/hw/arm/exynos4_boards.c @@ -0,0 +1,169 @@ +/* + * Samsung exynos4 SoC based boards emulation + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. All rights reserved. + * Maksim Kozlov <m.kozlov@samsung.com> + * Evgeny Voevodin <e.voevodin@samsung.com> + * Igor Mitsyanko <i.mitsyanko@samsung.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "sysemu/sysemu.h" +#include "sysemu/qtest.h" +#include "hw/sysbus.h" +#include "net/net.h" +#include "hw/arm/arm.h" +#include "exec/address-spaces.h" +#include "hw/arm/exynos4210.h" +#include "hw/boards.h" + +#undef DEBUG + +//#define DEBUG + +#ifdef DEBUG + #undef PRINT_DEBUG + #define PRINT_DEBUG(fmt, args...) \ + do { \ + fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ + } while (0) +#else + #define PRINT_DEBUG(fmt, args...) do {} while (0) +#endif + +#define SMDK_LAN9118_BASE_ADDR 0x05000000 + +typedef enum Exynos4BoardType { + EXYNOS4_BOARD_NURI, + EXYNOS4_BOARD_SMDKC210, + EXYNOS4_NUM_OF_BOARDS +} Exynos4BoardType; + +static int exynos4_board_id[EXYNOS4_NUM_OF_BOARDS] = { + [EXYNOS4_BOARD_NURI] = 0xD33, + [EXYNOS4_BOARD_SMDKC210] = 0xB16, +}; + +static int exynos4_board_smp_bootreg_addr[EXYNOS4_NUM_OF_BOARDS] = { + [EXYNOS4_BOARD_NURI] = EXYNOS4210_SECOND_CPU_BOOTREG, + [EXYNOS4_BOARD_SMDKC210] = EXYNOS4210_SECOND_CPU_BOOTREG, +}; + +static unsigned long exynos4_board_ram_size[EXYNOS4_NUM_OF_BOARDS] = { + [EXYNOS4_BOARD_NURI] = 0x40000000, + [EXYNOS4_BOARD_SMDKC210] = 0x40000000, +}; + +static struct arm_boot_info exynos4_board_binfo = { + .loader_start = EXYNOS4210_BASE_BOOT_ADDR, + .smp_loader_start = EXYNOS4210_SMP_BOOT_ADDR, + .nb_cpus = EXYNOS4210_NCPUS, + .write_secondary_boot = exynos4210_write_secondary, +}; + +static QEMUMachine exynos4_machines[EXYNOS4_NUM_OF_BOARDS]; + +static void lan9215_init(uint32_t base, qemu_irq irq) +{ + DeviceState *dev; + SysBusDevice *s; + + /* This should be a 9215 but the 9118 is close enough */ + if (nd_table[0].used) { + qemu_check_nic_model(&nd_table[0], "lan9118"); + dev = qdev_create(NULL, "lan9118"); + qdev_set_nic_properties(dev, &nd_table[0]); + qdev_prop_set_uint32(dev, "mode_16bit", 1); + qdev_init_nofail(dev); + s = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(s, 0, base); + sysbus_connect_irq(s, 0, irq); + } +} + +static Exynos4210State *exynos4_boards_init_common(MachineState *machine, + Exynos4BoardType board_type) +{ + if (smp_cpus != EXYNOS4210_NCPUS && !qtest_enabled()) { + fprintf(stderr, "%s board supports only %d CPU cores. Ignoring smp_cpus" + " value.\n", + exynos4_machines[board_type].name, + exynos4_machines[board_type].max_cpus); + } + + exynos4_board_binfo.ram_size = exynos4_board_ram_size[board_type]; + exynos4_board_binfo.board_id = exynos4_board_id[board_type]; + exynos4_board_binfo.smp_bootreg_addr = + exynos4_board_smp_bootreg_addr[board_type]; + exynos4_board_binfo.kernel_filename = machine->kernel_filename; + exynos4_board_binfo.initrd_filename = machine->initrd_filename; + exynos4_board_binfo.kernel_cmdline = machine->kernel_cmdline; + exynos4_board_binfo.gic_cpu_if_addr = + EXYNOS4210_SMP_PRIVATE_BASE_ADDR + 0x100; + + PRINT_DEBUG("\n ram_size: %luMiB [0x%08lx]\n" + " kernel_filename: %s\n" + " kernel_cmdline: %s\n" + " initrd_filename: %s\n", + exynos4_board_ram_size[board_type] / 1048576, + exynos4_board_ram_size[board_type], + machine->kernel_filename, + machine->kernel_cmdline, + machine->initrd_filename); + + return exynos4210_init(get_system_memory(), + exynos4_board_ram_size[board_type]); +} + +static void nuri_init(MachineState *machine) +{ + exynos4_boards_init_common(machine, EXYNOS4_BOARD_NURI); + + arm_load_kernel(ARM_CPU(first_cpu), &exynos4_board_binfo); +} + +static void smdkc210_init(MachineState *machine) +{ + Exynos4210State *s = exynos4_boards_init_common(machine, + EXYNOS4_BOARD_SMDKC210); + + lan9215_init(SMDK_LAN9118_BASE_ADDR, + qemu_irq_invert(s->irq_table[exynos4210_get_irq(37, 1)])); + arm_load_kernel(ARM_CPU(first_cpu), &exynos4_board_binfo); +} + +static QEMUMachine exynos4_machines[EXYNOS4_NUM_OF_BOARDS] = { + [EXYNOS4_BOARD_NURI] = { + .name = "nuri", + .desc = "Samsung NURI board (Exynos4210)", + .init = nuri_init, + .max_cpus = EXYNOS4210_NCPUS, + }, + [EXYNOS4_BOARD_SMDKC210] = { + .name = "smdkc210", + .desc = "Samsung SMDKC210 board (Exynos4210)", + .init = smdkc210_init, + .max_cpus = EXYNOS4210_NCPUS, + }, +}; + +static void exynos4_machine_init(void) +{ + qemu_register_machine(&exynos4_machines[EXYNOS4_BOARD_NURI]); + qemu_register_machine(&exynos4_machines[EXYNOS4_BOARD_SMDKC210]); +} + +machine_init(exynos4_machine_init); diff --git a/qemu/hw/arm/gumstix.c b/qemu/hw/arm/gumstix.c new file mode 100644 index 000000000..8103278b1 --- /dev/null +++ b/qemu/hw/arm/gumstix.c @@ -0,0 +1,142 @@ +/* + * Gumstix Platforms + * + * Copyright (c) 2007 by Thorsten Zitterell <info@bitmux.org> + * + * Code based on spitz platform by Andrzej Zaborowski <balrog@zabor.org> + * + * This code is licensed under the GNU GPL v2. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +/* + * Example usage: + * + * connex: + * ======= + * create image: + * # dd of=flash bs=1k count=16k if=/dev/zero + * # dd of=flash bs=1k conv=notrunc if=u-boot.bin + * # dd of=flash bs=1k conv=notrunc seek=256 if=rootfs.arm_nofpu.jffs2 + * start it: + * # qemu-system-arm -M connex -pflash flash -monitor null -nographic + * + * verdex: + * ======= + * create image: + * # dd of=flash bs=1k count=32k if=/dev/zero + * # dd of=flash bs=1k conv=notrunc if=u-boot.bin + * # dd of=flash bs=1k conv=notrunc seek=256 if=rootfs.arm_nofpu.jffs2 + * # dd of=flash bs=1k conv=notrunc seek=31744 if=uImage + * start it: + * # qemu-system-arm -M verdex -pflash flash -monitor null -nographic -m 289 + */ + +#include "hw/hw.h" +#include "hw/arm/pxa.h" +#include "net/net.h" +#include "hw/block/flash.h" +#include "hw/devices.h" +#include "hw/boards.h" +#include "sysemu/block-backend.h" +#include "exec/address-spaces.h" +#include "sysemu/qtest.h" + +static const int sector_len = 128 * 1024; + +static void connex_init(MachineState *machine) +{ + PXA2xxState *cpu; + DriveInfo *dinfo; + int be; + MemoryRegion *address_space_mem = get_system_memory(); + + uint32_t connex_rom = 0x01000000; + uint32_t connex_ram = 0x04000000; + + cpu = pxa255_init(address_space_mem, connex_ram); + + dinfo = drive_get(IF_PFLASH, 0, 0); + if (!dinfo && !qtest_enabled()) { + fprintf(stderr, "A flash image must be given with the " + "'pflash' parameter\n"); + exit(1); + } + +#ifdef TARGET_WORDS_BIGENDIAN + be = 1; +#else + be = 0; +#endif + if (!pflash_cfi01_register(0x00000000, NULL, "connext.rom", connex_rom, + dinfo ? blk_by_legacy_dinfo(dinfo) : NULL, + sector_len, connex_rom / sector_len, + 2, 0, 0, 0, 0, be)) { + fprintf(stderr, "qemu: Error registering flash memory.\n"); + exit(1); + } + + /* Interrupt line of NIC is connected to GPIO line 36 */ + smc91c111_init(&nd_table[0], 0x04000300, + qdev_get_gpio_in(cpu->gpio, 36)); +} + +static void verdex_init(MachineState *machine) +{ + const char *cpu_model = machine->cpu_model; + PXA2xxState *cpu; + DriveInfo *dinfo; + int be; + MemoryRegion *address_space_mem = get_system_memory(); + + uint32_t verdex_rom = 0x02000000; + uint32_t verdex_ram = 0x10000000; + + cpu = pxa270_init(address_space_mem, verdex_ram, cpu_model ?: "pxa270-c0"); + + dinfo = drive_get(IF_PFLASH, 0, 0); + if (!dinfo && !qtest_enabled()) { + fprintf(stderr, "A flash image must be given with the " + "'pflash' parameter\n"); + exit(1); + } + +#ifdef TARGET_WORDS_BIGENDIAN + be = 1; +#else + be = 0; +#endif + if (!pflash_cfi01_register(0x00000000, NULL, "verdex.rom", verdex_rom, + dinfo ? blk_by_legacy_dinfo(dinfo) : NULL, + sector_len, verdex_rom / sector_len, + 2, 0, 0, 0, 0, be)) { + fprintf(stderr, "qemu: Error registering flash memory.\n"); + exit(1); + } + + /* Interrupt line of NIC is connected to GPIO line 99 */ + smc91c111_init(&nd_table[0], 0x04000300, + qdev_get_gpio_in(cpu->gpio, 99)); +} + +static QEMUMachine connex_machine = { + .name = "connex", + .desc = "Gumstix Connex (PXA255)", + .init = connex_init, +}; + +static QEMUMachine verdex_machine = { + .name = "verdex", + .desc = "Gumstix Verdex (PXA270)", + .init = verdex_init, +}; + +static void gumstix_machine_init(void) +{ + qemu_register_machine(&connex_machine); + qemu_register_machine(&verdex_machine); +} + +machine_init(gumstix_machine_init); diff --git a/qemu/hw/arm/highbank.c b/qemu/hw/arm/highbank.c new file mode 100644 index 000000000..f8353a787 --- /dev/null +++ b/qemu/hw/arm/highbank.c @@ -0,0 +1,416 @@ +/* + * Calxeda Highbank SoC emulation + * + * Copyright (c) 2010-2012 Calxeda + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2 or later, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "hw/sysbus.h" +#include "hw/arm/arm.h" +#include "hw/devices.h" +#include "hw/loader.h" +#include "net/net.h" +#include "sysemu/sysemu.h" +#include "hw/boards.h" +#include "sysemu/block-backend.h" +#include "exec/address-spaces.h" +#include "qemu/error-report.h" + +#define SMP_BOOT_ADDR 0x100 +#define SMP_BOOT_REG 0x40 +#define MPCORE_PERIPHBASE 0xfff10000 + +#define NIRQ_GIC 160 + +/* Board init. */ + +static void hb_write_secondary(ARMCPU *cpu, const struct arm_boot_info *info) +{ + int n; + uint32_t smpboot[] = { + 0xee100fb0, /* mrc p15, 0, r0, c0, c0, 5 - read current core id */ + 0xe210000f, /* ands r0, r0, #0x0f */ + 0xe3a03040, /* mov r3, #0x40 - jump address is 0x40 + 0x10 * core id */ + 0xe0830200, /* add r0, r3, r0, lsl #4 */ + 0xe59f2024, /* ldr r2, privbase */ + 0xe3a01001, /* mov r1, #1 */ + 0xe5821100, /* str r1, [r2, #256] - set GICC_CTLR.Enable */ + 0xe3a010ff, /* mov r1, #0xff */ + 0xe5821104, /* str r1, [r2, #260] - set GICC_PMR.Priority to 0xff */ + 0xf57ff04f, /* dsb */ + 0xe320f003, /* wfi */ + 0xe5901000, /* ldr r1, [r0] */ + 0xe1110001, /* tst r1, r1 */ + 0x0afffffb, /* beq <wfi> */ + 0xe12fff11, /* bx r1 */ + MPCORE_PERIPHBASE /* privbase: MPCore peripheral base address. */ + }; + for (n = 0; n < ARRAY_SIZE(smpboot); n++) { + smpboot[n] = tswap32(smpboot[n]); + } + rom_add_blob_fixed("smpboot", smpboot, sizeof(smpboot), SMP_BOOT_ADDR); +} + +static void hb_reset_secondary(ARMCPU *cpu, const struct arm_boot_info *info) +{ + CPUARMState *env = &cpu->env; + + switch (info->nb_cpus) { + case 4: + address_space_stl_notdirty(&address_space_memory, + SMP_BOOT_REG + 0x30, 0, + MEMTXATTRS_UNSPECIFIED, NULL); + case 3: + address_space_stl_notdirty(&address_space_memory, + SMP_BOOT_REG + 0x20, 0, + MEMTXATTRS_UNSPECIFIED, NULL); + case 2: + address_space_stl_notdirty(&address_space_memory, + SMP_BOOT_REG + 0x10, 0, + MEMTXATTRS_UNSPECIFIED, NULL); + env->regs[15] = SMP_BOOT_ADDR; + break; + default: + break; + } +} + +#define NUM_REGS 0x200 +static void hb_regs_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + uint32_t *regs = opaque; + + if (offset == 0xf00) { + if (value == 1 || value == 2) { + qemu_system_reset_request(); + } else if (value == 3) { + qemu_system_shutdown_request(); + } + } + + regs[offset/4] = value; +} + +static uint64_t hb_regs_read(void *opaque, hwaddr offset, + unsigned size) +{ + uint32_t *regs = opaque; + uint32_t value = regs[offset/4]; + + if ((offset == 0x100) || (offset == 0x108) || (offset == 0x10C)) { + value |= 0x30000000; + } + + return value; +} + +static const MemoryRegionOps hb_mem_ops = { + .read = hb_regs_read, + .write = hb_regs_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +#define TYPE_HIGHBANK_REGISTERS "highbank-regs" +#define HIGHBANK_REGISTERS(obj) \ + OBJECT_CHECK(HighbankRegsState, (obj), TYPE_HIGHBANK_REGISTERS) + +typedef struct { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion iomem; + uint32_t regs[NUM_REGS]; +} HighbankRegsState; + +static VMStateDescription vmstate_highbank_regs = { + .name = "highbank-regs", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, HighbankRegsState, NUM_REGS), + VMSTATE_END_OF_LIST(), + }, +}; + +static void highbank_regs_reset(DeviceState *dev) +{ + HighbankRegsState *s = HIGHBANK_REGISTERS(dev); + + s->regs[0x40] = 0x05F20121; + s->regs[0x41] = 0x2; + s->regs[0x42] = 0x05F30121; + s->regs[0x43] = 0x05F40121; +} + +static int highbank_regs_init(SysBusDevice *dev) +{ + HighbankRegsState *s = HIGHBANK_REGISTERS(dev); + + memory_region_init_io(&s->iomem, OBJECT(s), &hb_mem_ops, s->regs, + "highbank_regs", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + + return 0; +} + +static void highbank_regs_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + sbc->init = highbank_regs_init; + dc->desc = "Calxeda Highbank registers"; + dc->vmsd = &vmstate_highbank_regs; + dc->reset = highbank_regs_reset; +} + +static const TypeInfo highbank_regs_info = { + .name = TYPE_HIGHBANK_REGISTERS, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(HighbankRegsState), + .class_init = highbank_regs_class_init, +}; + +static void highbank_regs_register_types(void) +{ + type_register_static(&highbank_regs_info); +} + +type_init(highbank_regs_register_types) + +static struct arm_boot_info highbank_binfo; + +enum cxmachines { + CALXEDA_HIGHBANK, + CALXEDA_MIDWAY, +}; + +/* ram_size must be set to match the upper bound of memory in the + * device tree (linux/arch/arm/boot/dts/highbank.dts), which is + * normally 0xff900000 or -m 4089. When running this board on a + * 32-bit host, set the reg value of memory to 0xf7ff00000 in the + * device tree and pass -m 2047 to QEMU. + */ +static void calxeda_init(MachineState *machine, enum cxmachines machine_id) +{ + ram_addr_t ram_size = machine->ram_size; + const char *cpu_model = machine->cpu_model; + const char *kernel_filename = machine->kernel_filename; + const char *kernel_cmdline = machine->kernel_cmdline; + const char *initrd_filename = machine->initrd_filename; + DeviceState *dev = NULL; + SysBusDevice *busdev; + qemu_irq pic[128]; + int n; + qemu_irq cpu_irq[4]; + qemu_irq cpu_fiq[4]; + MemoryRegion *sysram; + MemoryRegion *dram; + MemoryRegion *sysmem; + char *sysboot_filename; + + if (!cpu_model) { + switch (machine_id) { + case CALXEDA_HIGHBANK: + cpu_model = "cortex-a9"; + break; + case CALXEDA_MIDWAY: + cpu_model = "cortex-a15"; + break; + } + } + + for (n = 0; n < smp_cpus; n++) { + ObjectClass *oc = cpu_class_by_name(TYPE_ARM_CPU, cpu_model); + Object *cpuobj; + ARMCPU *cpu; + Error *err = NULL; + + if (!oc) { + error_report("Unable to find CPU definition"); + exit(1); + } + + cpuobj = object_new(object_class_get_name(oc)); + cpu = ARM_CPU(cpuobj); + + /* By default A9 and A15 CPUs have EL3 enabled. This board does not + * currently support EL3 so the CPU EL3 property is disabled before + * realization. + */ + if (object_property_find(cpuobj, "has_el3", NULL)) { + object_property_set_bool(cpuobj, false, "has_el3", &err); + if (err) { + error_report_err(err); + exit(1); + } + } + + if (object_property_find(cpuobj, "reset-cbar", NULL)) { + object_property_set_int(cpuobj, MPCORE_PERIPHBASE, + "reset-cbar", &error_abort); + } + object_property_set_bool(cpuobj, true, "realized", &err); + if (err) { + error_report_err(err); + exit(1); + } + cpu_irq[n] = qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ); + cpu_fiq[n] = qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_FIQ); + } + + sysmem = get_system_memory(); + dram = g_new(MemoryRegion, 1); + memory_region_allocate_system_memory(dram, NULL, "highbank.dram", ram_size); + /* SDRAM at address zero. */ + memory_region_add_subregion(sysmem, 0, dram); + + sysram = g_new(MemoryRegion, 1); + memory_region_init_ram(sysram, NULL, "highbank.sysram", 0x8000, + &error_abort); + memory_region_add_subregion(sysmem, 0xfff88000, sysram); + if (bios_name != NULL) { + sysboot_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); + if (sysboot_filename != NULL) { + if (load_image_targphys(sysboot_filename, 0xfff88000, 0x8000) < 0) { + hw_error("Unable to load %s\n", bios_name); + } + g_free(sysboot_filename); + } else { + hw_error("Unable to find %s\n", bios_name); + } + } + + switch (machine_id) { + case CALXEDA_HIGHBANK: + dev = qdev_create(NULL, "l2x0"); + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(busdev, 0, 0xfff12000); + + dev = qdev_create(NULL, "a9mpcore_priv"); + break; + case CALXEDA_MIDWAY: + dev = qdev_create(NULL, "a15mpcore_priv"); + break; + } + qdev_prop_set_uint32(dev, "num-cpu", smp_cpus); + qdev_prop_set_uint32(dev, "num-irq", NIRQ_GIC); + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(busdev, 0, MPCORE_PERIPHBASE); + for (n = 0; n < smp_cpus; n++) { + sysbus_connect_irq(busdev, n, cpu_irq[n]); + sysbus_connect_irq(busdev, n + smp_cpus, cpu_fiq[n]); + } + + for (n = 0; n < 128; n++) { + pic[n] = qdev_get_gpio_in(dev, n); + } + + dev = qdev_create(NULL, "sp804"); + qdev_prop_set_uint32(dev, "freq0", 150000000); + qdev_prop_set_uint32(dev, "freq1", 150000000); + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(busdev, 0, 0xfff34000); + sysbus_connect_irq(busdev, 0, pic[18]); + sysbus_create_simple("pl011", 0xfff36000, pic[20]); + + dev = qdev_create(NULL, "highbank-regs"); + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(busdev, 0, 0xfff3c000); + + sysbus_create_simple("pl061", 0xfff30000, pic[14]); + sysbus_create_simple("pl061", 0xfff31000, pic[15]); + sysbus_create_simple("pl061", 0xfff32000, pic[16]); + sysbus_create_simple("pl061", 0xfff33000, pic[17]); + sysbus_create_simple("pl031", 0xfff35000, pic[19]); + sysbus_create_simple("pl022", 0xfff39000, pic[23]); + + sysbus_create_simple("sysbus-ahci", 0xffe08000, pic[83]); + + if (nd_table[0].used) { + qemu_check_nic_model(&nd_table[0], "xgmac"); + dev = qdev_create(NULL, "xgmac"); + qdev_set_nic_properties(dev, &nd_table[0]); + qdev_init_nofail(dev); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xfff50000); + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[77]); + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 1, pic[78]); + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 2, pic[79]); + + qemu_check_nic_model(&nd_table[1], "xgmac"); + dev = qdev_create(NULL, "xgmac"); + qdev_set_nic_properties(dev, &nd_table[1]); + qdev_init_nofail(dev); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xfff51000); + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[80]); + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 1, pic[81]); + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 2, pic[82]); + } + + highbank_binfo.ram_size = ram_size; + highbank_binfo.kernel_filename = kernel_filename; + highbank_binfo.kernel_cmdline = kernel_cmdline; + highbank_binfo.initrd_filename = initrd_filename; + /* highbank requires a dtb in order to boot, and the dtb will override + * the board ID. The following value is ignored, so set it to -1 to be + * clear that the value is meaningless. + */ + highbank_binfo.board_id = -1; + highbank_binfo.nb_cpus = smp_cpus; + highbank_binfo.loader_start = 0; + highbank_binfo.write_secondary_boot = hb_write_secondary; + highbank_binfo.secondary_cpu_reset_hook = hb_reset_secondary; + arm_load_kernel(ARM_CPU(first_cpu), &highbank_binfo); +} + +static void highbank_init(MachineState *machine) +{ + calxeda_init(machine, CALXEDA_HIGHBANK); +} + +static void midway_init(MachineState *machine) +{ + calxeda_init(machine, CALXEDA_MIDWAY); +} + +static QEMUMachine highbank_machine = { + .name = "highbank", + .desc = "Calxeda Highbank (ECX-1000)", + .init = highbank_init, + .block_default_type = IF_SCSI, + .max_cpus = 4, +}; + +static QEMUMachine midway_machine = { + .name = "midway", + .desc = "Calxeda Midway (ECX-2000)", + .init = midway_init, + .block_default_type = IF_SCSI, + .max_cpus = 4, +}; + +static void calxeda_machines_init(void) +{ + qemu_register_machine(&highbank_machine); + qemu_register_machine(&midway_machine); +} + +machine_init(calxeda_machines_init); diff --git a/qemu/hw/arm/integratorcp.c b/qemu/hw/arm/integratorcp.c new file mode 100644 index 000000000..0fbbf997e --- /dev/null +++ b/qemu/hw/arm/integratorcp.c @@ -0,0 +1,684 @@ +/* + * ARM Integrator CP System emulation. + * + * Copyright (c) 2005-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the GPL + */ + +#include "hw/sysbus.h" +#include "hw/devices.h" +#include "hw/boards.h" +#include "hw/arm/arm.h" +#include "hw/misc/arm_integrator_debug.h" +#include "net/net.h" +#include "exec/address-spaces.h" +#include "sysemu/sysemu.h" +#include "qemu/error-report.h" + +#define TYPE_INTEGRATOR_CM "integrator_core" +#define INTEGRATOR_CM(obj) \ + OBJECT_CHECK(IntegratorCMState, (obj), TYPE_INTEGRATOR_CM) + +typedef struct IntegratorCMState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion iomem; + uint32_t memsz; + MemoryRegion flash; + 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 cm_refcnt_offset; + uint32_t int_level; + uint32_t irq_enabled; + uint32_t fiq_enabled; +} IntegratorCMState; + +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 uint64_t integratorcm_read(void *opaque, hwaddr offset, + unsigned size) +{ + IntegratorCMState *s = opaque; + 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. */ + hw_error("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_REFCNT */ + /* This register, CM_REFCNT, provides a 32-bit count value. + * The count increments at the fixed reference clock frequency of 24MHz + * and can be used as a real-time counter. + */ + return (uint32_t)muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), 24, + 1000) - s->cm_refcnt_offset; + 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: + hw_error("integratorcm_read: Unimplemented offset 0x%x\n", + (int)offset); + return 0; + } +} + +static void integratorcm_do_remap(IntegratorCMState *s) +{ + /* Sync memory region state with CM_CTRL REMAP bit: + * bit 0 => flash at address 0; bit 1 => RAM + */ + memory_region_set_enabled(&s->flash, !(s->cm_ctrl & 4)); +} + +static void integratorcm_set_ctrl(IntegratorCMState *s, uint32_t value) +{ + if (value & 8) { + qemu_system_reset_request(); + } + if ((s->cm_ctrl ^ value) & 1) { + /* (value & 1) != 0 means the green "MISC LED" is lit. + * We don't have any nice place to display LEDs. printf is a bad + * idea because Linux uses the LED as a heartbeat and the output + * will swamp anything else on the terminal. + */ + } + /* Note that the RESET bit [3] always reads as zero */ + s->cm_ctrl = (s->cm_ctrl & ~5) | (value & 5); + integratorcm_do_remap(s); +} + +static void integratorcm_update(IntegratorCMState *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)) + hw_error("Core module interrupt\n"); +} + +static void integratorcm_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + IntegratorCMState *s = opaque; + 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: + hw_error("integratorcm_write: Unimplemented offset 0x%x\n", + (int)offset); + break; + } +} + +/* Integrator/CM control registers. */ + +static const MemoryRegionOps integratorcm_ops = { + .read = integratorcm_read, + .write = integratorcm_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int integratorcm_init(SysBusDevice *dev) +{ + IntegratorCMState *s = INTEGRATOR_CM(dev); + + s->cm_osc = 0x01000048; + /* ??? What should the high bits of this value be? */ + s->cm_auxosc = 0x0007feff; + s->cm_sdram = 0x00011122; + if (s->memsz >= 256) { + integrator_spd[31] = 64; + s->cm_sdram |= 0x10; + } else if (s->memsz >= 128) { + integrator_spd[31] = 32; + s->cm_sdram |= 0x0c; + } else if (s->memsz >= 64) { + integrator_spd[31] = 16; + s->cm_sdram |= 0x08; + } else if (s->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->cm_refcnt_offset = muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), 24, + 1000); + memory_region_init_ram(&s->flash, OBJECT(s), "integrator.flash", 0x100000, + &error_abort); + vmstate_register_ram_global(&s->flash); + + memory_region_init_io(&s->iomem, OBJECT(s), &integratorcm_ops, s, + "integratorcm", 0x00800000); + sysbus_init_mmio(dev, &s->iomem); + + integratorcm_do_remap(s); + /* ??? Save/restore. */ + return 0; +} + +/* Integrator/CP hardware emulation. */ +/* Primary interrupt controller. */ + +#define TYPE_INTEGRATOR_PIC "integrator_pic" +#define INTEGRATOR_PIC(obj) \ + OBJECT_CHECK(icp_pic_state, (obj), TYPE_INTEGRATOR_PIC) + +typedef struct icp_pic_state { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion iomem; + uint32_t level; + uint32_t irq_enabled; + uint32_t fiq_enabled; + qemu_irq parent_irq; + qemu_irq parent_fiq; +} icp_pic_state; + +static void icp_pic_update(icp_pic_state *s) +{ + uint32_t flags; + + flags = (s->level & s->irq_enabled); + qemu_set_irq(s->parent_irq, flags != 0); + flags = (s->level & s->fiq_enabled); + qemu_set_irq(s->parent_fiq, flags != 0); +} + +static void icp_pic_set_irq(void *opaque, int irq, int level) +{ + icp_pic_state *s = (icp_pic_state *)opaque; + if (level) + s->level |= 1 << irq; + else + s->level &= ~(1 << irq); + icp_pic_update(s); +} + +static uint64_t icp_pic_read(void *opaque, hwaddr offset, + unsigned size) +{ + icp_pic_state *s = (icp_pic_state *)opaque; + + 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", (int)offset); + return 0; + } +} + +static void icp_pic_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + icp_pic_state *s = (icp_pic_state *)opaque; + + 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_irq(s, 0, 1); + break; + case 5: /* INT_SOFTCLR */ + if (value & 1) + icp_pic_set_irq(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", (int)offset); + return; + } + icp_pic_update(s); +} + +static const MemoryRegionOps icp_pic_ops = { + .read = icp_pic_read, + .write = icp_pic_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int icp_pic_init(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + icp_pic_state *s = INTEGRATOR_PIC(dev); + + qdev_init_gpio_in(dev, icp_pic_set_irq, 32); + sysbus_init_irq(sbd, &s->parent_irq); + sysbus_init_irq(sbd, &s->parent_fiq); + memory_region_init_io(&s->iomem, OBJECT(s), &icp_pic_ops, s, + "icp-pic", 0x00800000); + sysbus_init_mmio(sbd, &s->iomem); + return 0; +} + +/* CP control registers. */ + +#define TYPE_ICP_CONTROL_REGS "icp-ctrl-regs" +#define ICP_CONTROL_REGS(obj) \ + OBJECT_CHECK(ICPCtrlRegsState, (obj), TYPE_ICP_CONTROL_REGS) + +typedef struct ICPCtrlRegsState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion iomem; + + qemu_irq mmc_irq; + uint32_t intreg_state; +} ICPCtrlRegsState; + +#define ICP_GPIO_MMC_WPROT "mmc-wprot" +#define ICP_GPIO_MMC_CARDIN "mmc-cardin" + +#define ICP_INTREG_WPROT (1 << 0) +#define ICP_INTREG_CARDIN (1 << 3) + +static uint64_t icp_control_read(void *opaque, hwaddr offset, + unsigned size) +{ + ICPCtrlRegsState *s = opaque; + + switch (offset >> 2) { + case 0: /* CP_IDFIELD */ + return 0x41034003; + case 1: /* CP_FLASHPROG */ + return 0; + case 2: /* CP_INTREG */ + return s->intreg_state; + case 3: /* CP_DECODE */ + return 0x11; + default: + hw_error("icp_control_read: Bad offset %x\n", (int)offset); + return 0; + } +} + +static void icp_control_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + ICPCtrlRegsState *s = opaque; + + switch (offset >> 2) { + case 2: /* CP_INTREG */ + s->intreg_state &= ~(value & ICP_INTREG_CARDIN); + qemu_set_irq(s->mmc_irq, !!(s->intreg_state & ICP_INTREG_CARDIN)); + break; + case 1: /* CP_FLASHPROG */ + case 3: /* CP_DECODE */ + /* Nothing interesting implemented yet. */ + break; + default: + hw_error("icp_control_write: Bad offset %x\n", (int)offset); + } +} + +static const MemoryRegionOps icp_control_ops = { + .read = icp_control_read, + .write = icp_control_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void icp_control_mmc_wprot(void *opaque, int line, int level) +{ + ICPCtrlRegsState *s = opaque; + + s->intreg_state &= ~ICP_INTREG_WPROT; + if (level) { + s->intreg_state |= ICP_INTREG_WPROT; + } +} + +static void icp_control_mmc_cardin(void *opaque, int line, int level) +{ + ICPCtrlRegsState *s = opaque; + + /* line is released by writing to CP_INTREG */ + if (level) { + s->intreg_state |= ICP_INTREG_CARDIN; + qemu_set_irq(s->mmc_irq, 1); + } +} + +static void icp_control_init(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + ICPCtrlRegsState *s = ICP_CONTROL_REGS(obj); + DeviceState *dev = DEVICE(obj); + + memory_region_init_io(&s->iomem, OBJECT(s), &icp_control_ops, s, + "icp_ctrl_regs", 0x00800000); + sysbus_init_mmio(sbd, &s->iomem); + + qdev_init_gpio_in_named(dev, icp_control_mmc_wprot, ICP_GPIO_MMC_WPROT, 1); + qdev_init_gpio_in_named(dev, icp_control_mmc_cardin, + ICP_GPIO_MMC_CARDIN, 1); + sysbus_init_irq(sbd, &s->mmc_irq); +} + + +/* Board init. */ + +static struct arm_boot_info integrator_binfo = { + .loader_start = 0x0, + .board_id = 0x113, +}; + +static void integratorcp_init(MachineState *machine) +{ + ram_addr_t ram_size = machine->ram_size; + const char *cpu_model = machine->cpu_model; + const char *kernel_filename = machine->kernel_filename; + const char *kernel_cmdline = machine->kernel_cmdline; + const char *initrd_filename = machine->initrd_filename; + ObjectClass *cpu_oc; + Object *cpuobj; + ARMCPU *cpu; + MemoryRegion *address_space_mem = get_system_memory(); + MemoryRegion *ram = g_new(MemoryRegion, 1); + MemoryRegion *ram_alias = g_new(MemoryRegion, 1); + qemu_irq pic[32]; + DeviceState *dev, *sic, *icp; + int i; + Error *err = NULL; + + if (!cpu_model) { + cpu_model = "arm926"; + } + + cpu_oc = cpu_class_by_name(TYPE_ARM_CPU, cpu_model); + if (!cpu_oc) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + + cpuobj = object_new(object_class_get_name(cpu_oc)); + + /* By default ARM1176 CPUs have EL3 enabled. This board does not + * currently support EL3 so the CPU EL3 property is disabled before + * realization. + */ + if (object_property_find(cpuobj, "has_el3", NULL)) { + object_property_set_bool(cpuobj, false, "has_el3", &err); + if (err) { + error_report_err(err); + exit(1); + } + } + + object_property_set_bool(cpuobj, true, "realized", &err); + if (err) { + error_report_err(err); + exit(1); + } + + cpu = ARM_CPU(cpuobj); + + memory_region_allocate_system_memory(ram, NULL, "integrator.ram", + ram_size); + /* ??? On a real system the first 1Mb is mapped as SSRAM or boot flash. */ + /* ??? RAM should repeat to fill physical memory space. */ + /* SDRAM at address zero*/ + memory_region_add_subregion(address_space_mem, 0, ram); + /* And again at address 0x80000000 */ + memory_region_init_alias(ram_alias, NULL, "ram.alias", ram, 0, ram_size); + memory_region_add_subregion(address_space_mem, 0x80000000, ram_alias); + + dev = qdev_create(NULL, TYPE_INTEGRATOR_CM); + qdev_prop_set_uint32(dev, "memsz", ram_size >> 20); + qdev_init_nofail(dev); + sysbus_mmio_map((SysBusDevice *)dev, 0, 0x10000000); + + dev = sysbus_create_varargs(TYPE_INTEGRATOR_PIC, 0x14000000, + qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ), + qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_FIQ), + NULL); + for (i = 0; i < 32; i++) { + pic[i] = qdev_get_gpio_in(dev, i); + } + sic = sysbus_create_simple(TYPE_INTEGRATOR_PIC, 0xca000000, pic[26]); + sysbus_create_varargs("integrator_pit", 0x13000000, + pic[5], pic[6], pic[7], NULL); + sysbus_create_simple("pl031", 0x15000000, pic[8]); + sysbus_create_simple("pl011", 0x16000000, pic[1]); + sysbus_create_simple("pl011", 0x17000000, pic[2]); + icp = sysbus_create_simple(TYPE_ICP_CONTROL_REGS, 0xcb000000, + qdev_get_gpio_in(sic, 3)); + sysbus_create_simple("pl050_keyboard", 0x18000000, pic[3]); + sysbus_create_simple("pl050_mouse", 0x19000000, pic[4]); + sysbus_create_simple(TYPE_INTEGRATOR_DEBUG, 0x1a000000, 0); + + dev = sysbus_create_varargs("pl181", 0x1c000000, pic[23], pic[24], NULL); + qdev_connect_gpio_out(dev, 0, + qdev_get_gpio_in_named(icp, ICP_GPIO_MMC_WPROT, 0)); + qdev_connect_gpio_out(dev, 1, + qdev_get_gpio_in_named(icp, ICP_GPIO_MMC_CARDIN, 0)); + + if (nd_table[0].used) + smc91c111_init(&nd_table[0], 0xc8000000, pic[27]); + + sysbus_create_simple("pl110", 0xc0000000, pic[22]); + + integrator_binfo.ram_size = ram_size; + integrator_binfo.kernel_filename = kernel_filename; + integrator_binfo.kernel_cmdline = kernel_cmdline; + integrator_binfo.initrd_filename = initrd_filename; + arm_load_kernel(cpu, &integrator_binfo); +} + +static QEMUMachine integratorcp_machine = { + .name = "integratorcp", + .desc = "ARM Integrator/CP (ARM926EJ-S)", + .init = integratorcp_init, +}; + +static void integratorcp_machine_init(void) +{ + qemu_register_machine(&integratorcp_machine); +} + +machine_init(integratorcp_machine_init); + +static Property core_properties[] = { + DEFINE_PROP_UINT32("memsz", IntegratorCMState, memsz, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void core_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = integratorcm_init; + dc->props = core_properties; +} + +static const TypeInfo core_info = { + .name = TYPE_INTEGRATOR_CM, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IntegratorCMState), + .class_init = core_class_init, +}; + +static void icp_pic_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + + sdc->init = icp_pic_init; +} + +static const TypeInfo icp_pic_info = { + .name = TYPE_INTEGRATOR_PIC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(icp_pic_state), + .class_init = icp_pic_class_init, +}; + +static const TypeInfo icp_ctrl_regs_info = { + .name = TYPE_ICP_CONTROL_REGS, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(ICPCtrlRegsState), + .instance_init = icp_control_init, +}; + +static void integratorcp_register_types(void) +{ + type_register_static(&icp_pic_info); + type_register_static(&core_info); + type_register_static(&icp_ctrl_regs_info); +} + +type_init(integratorcp_register_types) diff --git a/qemu/hw/arm/kzm.c b/qemu/hw/arm/kzm.c new file mode 100644 index 000000000..5be0369a5 --- /dev/null +++ b/qemu/hw/arm/kzm.c @@ -0,0 +1,153 @@ +/* + * KZM Board System emulation. + * + * Copyright (c) 2008 OKL and 2011 NICTA + * Written by Hans at OK-Labs + * Updated by Peter Chubb. + * + * This code is licensed under the GPL, version 2 or later. + * See the file `COPYING' in the top level directory. + * + * It (partially) emulates a Kyoto Microcomputer + * KZM-ARM11-01 evaluation board, with a Freescale + * i.MX31 SoC + */ + +#include "hw/sysbus.h" +#include "exec/address-spaces.h" +#include "hw/hw.h" +#include "hw/arm/arm.h" +#include "hw/devices.h" +#include "net/net.h" +#include "sysemu/sysemu.h" +#include "hw/boards.h" +#include "hw/char/serial.h" +#include "hw/arm/imx.h" + + /* Memory map for Kzm Emulation Baseboard: + * 0x00000000-0x00003fff 16k secure ROM IGNORED + * 0x00004000-0x00407fff Reserved IGNORED + * 0x00404000-0x00407fff ROM IGNORED + * 0x00408000-0x0fffffff Reserved IGNORED + * 0x10000000-0x1fffbfff RAM aliasing IGNORED + * 0x1fffc000-0x1fffffff RAM EMULATED + * 0x20000000-0x2fffffff Reserved IGNORED + * 0x30000000-0x7fffffff I.MX31 Internal Register Space + * 0x43f00000 IO_AREA0 + * 0x43f90000 UART1 EMULATED + * 0x43f94000 UART2 EMULATED + * 0x68000000 AVIC EMULATED + * 0x53f80000 CCM EMULATED + * 0x53f94000 PIT 1 EMULATED + * 0x53f98000 PIT 2 EMULATED + * 0x53f90000 GPT EMULATED + * 0x80000000-0x87ffffff RAM EMULATED + * 0x88000000-0x8fffffff RAM Aliasing EMULATED + * 0xa0000000-0xafffffff NAND Flash IGNORED + * 0xb0000000-0xb3ffffff Unavailable IGNORED + * 0xb4000000-0xb4000fff 8-bit free space IGNORED + * 0xb4001000-0xb400100f Board control IGNORED + * 0xb4001003 DIP switch + * 0xb4001010-0xb400101f 7-segment LED IGNORED + * 0xb4001020-0xb400102f LED IGNORED + * 0xb4001030-0xb400103f LED IGNORED + * 0xb4001040-0xb400104f FPGA, UART EMULATED + * 0xb4001050-0xb400105f FPGA, UART EMULATED + * 0xb4001060-0xb40fffff FPGA IGNORED + * 0xb6000000-0xb61fffff LAN controller EMULATED + * 0xb6200000-0xb62fffff FPGA NAND Controller IGNORED + * 0xb6300000-0xb7ffffff Free IGNORED + * 0xb8000000-0xb8004fff Memory control registers IGNORED + * 0xc0000000-0xc3ffffff PCMCIA/CF IGNORED + * 0xc4000000-0xffffffff Reserved IGNORED + */ + +#define KZM_RAMADDRESS (0x80000000) +#define KZM_FPGA (0xb4001040) + +static struct arm_boot_info kzm_binfo = { + .loader_start = KZM_RAMADDRESS, + .board_id = 1722, +}; + +static void kzm_init(MachineState *machine) +{ + ram_addr_t ram_size = machine->ram_size; + const char *cpu_model = machine->cpu_model; + const char *kernel_filename = machine->kernel_filename; + const char *kernel_cmdline = machine->kernel_cmdline; + const char *initrd_filename = machine->initrd_filename; + ARMCPU *cpu; + MemoryRegion *address_space_mem = get_system_memory(); + MemoryRegion *ram = g_new(MemoryRegion, 1); + MemoryRegion *sram = g_new(MemoryRegion, 1); + MemoryRegion *ram_alias = g_new(MemoryRegion, 1); + DeviceState *dev; + DeviceState *ccm; + + if (!cpu_model) { + cpu_model = "arm1136"; + } + + cpu = cpu_arm_init(cpu_model); + if (!cpu) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + + /* On a real system, the first 16k is a `secure boot rom' */ + + memory_region_allocate_system_memory(ram, NULL, "kzm.ram", ram_size); + memory_region_add_subregion(address_space_mem, KZM_RAMADDRESS, ram); + + memory_region_init_alias(ram_alias, NULL, "ram.alias", ram, 0, ram_size); + memory_region_add_subregion(address_space_mem, 0x88000000, ram_alias); + + memory_region_init_ram(sram, NULL, "kzm.sram", 0x4000, &error_abort); + memory_region_add_subregion(address_space_mem, 0x1FFFC000, sram); + + dev = sysbus_create_varargs("imx_avic", 0x68000000, + qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ), + qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_FIQ), + NULL); + + imx_serial_create(0, 0x43f90000, qdev_get_gpio_in(dev, 45)); + imx_serial_create(1, 0x43f94000, qdev_get_gpio_in(dev, 32)); + + ccm = sysbus_create_simple("imx_ccm", 0x53f80000, NULL); + + imx_timerp_create(0x53f94000, qdev_get_gpio_in(dev, 28), ccm); + imx_timerp_create(0x53f98000, qdev_get_gpio_in(dev, 27), ccm); + imx_timerg_create(0x53f90000, qdev_get_gpio_in(dev, 29), ccm); + + if (nd_table[0].used) { + lan9118_init(&nd_table[0], 0xb6000000, qdev_get_gpio_in(dev, 52)); + } + + if (serial_hds[2]) { /* touchscreen */ + serial_mm_init(address_space_mem, KZM_FPGA+0x10, 0, + qdev_get_gpio_in(dev, 52), + 14745600, serial_hds[2], + DEVICE_NATIVE_ENDIAN); + } + + kzm_binfo.ram_size = ram_size; + kzm_binfo.kernel_filename = kernel_filename; + kzm_binfo.kernel_cmdline = kernel_cmdline; + kzm_binfo.initrd_filename = initrd_filename; + kzm_binfo.nb_cpus = 1; + arm_load_kernel(cpu, &kzm_binfo); +} + +static QEMUMachine kzm_machine = { + .name = "kzm", + .desc = "ARM KZM Emulation Baseboard (ARM1136)", + .init = kzm_init, +}; + +static void kzm_machine_init(void) +{ + qemu_register_machine(&kzm_machine); +} + +machine_init(kzm_machine_init) diff --git a/qemu/hw/arm/mainstone.c b/qemu/hw/arm/mainstone.c new file mode 100644 index 000000000..0da02a67e --- /dev/null +++ b/qemu/hw/arm/mainstone.c @@ -0,0 +1,202 @@ +/* + * PXA270-based Intel Mainstone platforms. + * + * Copyright (c) 2007 by Armin Kuster <akuster@kama-aina.net> or + * <akuster@mvista.com> + * + * Code based on spitz platform by Andrzej Zaborowski <balrog@zabor.org> + * + * This code is licensed under the GNU GPL v2. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ +#include "hw/hw.h" +#include "hw/arm/pxa.h" +#include "hw/arm/arm.h" +#include "net/net.h" +#include "hw/devices.h" +#include "hw/boards.h" +#include "hw/block/flash.h" +#include "sysemu/block-backend.h" +#include "hw/sysbus.h" +#include "exec/address-spaces.h" +#include "sysemu/qtest.h" + +/* Device addresses */ +#define MST_FPGA_PHYS 0x08000000 +#define MST_ETH_PHYS 0x10000300 +#define MST_FLASH_0 0x00000000 +#define MST_FLASH_1 0x04000000 + +/* IRQ definitions */ +#define MMC_IRQ 0 +#define USIM_IRQ 1 +#define USBC_IRQ 2 +#define ETHERNET_IRQ 3 +#define AC97_IRQ 4 +#define PEN_IRQ 5 +#define MSINS_IRQ 6 +#define EXBRD_IRQ 7 +#define S0_CD_IRQ 9 +#define S0_STSCHG_IRQ 10 +#define S0_IRQ 11 +#define S1_CD_IRQ 13 +#define S1_STSCHG_IRQ 14 +#define S1_IRQ 15 + +static const struct keymap map[0xE0] = { + [0 ... 0xDF] = { -1, -1 }, + [0x1e] = {0,0}, /* a */ + [0x30] = {0,1}, /* b */ + [0x2e] = {0,2}, /* c */ + [0x20] = {0,3}, /* d */ + [0x12] = {0,4}, /* e */ + [0x21] = {0,5}, /* f */ + [0x22] = {1,0}, /* g */ + [0x23] = {1,1}, /* h */ + [0x17] = {1,2}, /* i */ + [0x24] = {1,3}, /* j */ + [0x25] = {1,4}, /* k */ + [0x26] = {1,5}, /* l */ + [0x32] = {2,0}, /* m */ + [0x31] = {2,1}, /* n */ + [0x18] = {2,2}, /* o */ + [0x19] = {2,3}, /* p */ + [0x10] = {2,4}, /* q */ + [0x13] = {2,5}, /* r */ + [0x1f] = {3,0}, /* s */ + [0x14] = {3,1}, /* t */ + [0x16] = {3,2}, /* u */ + [0x2f] = {3,3}, /* v */ + [0x11] = {3,4}, /* w */ + [0x2d] = {3,5}, /* x */ + [0x15] = {4,2}, /* y */ + [0x2c] = {4,3}, /* z */ + [0xc7] = {5,0}, /* Home */ + [0x2a] = {5,1}, /* shift */ + /* + * There are two matrix positions which map to space, + * but QEMU can only use one of them for the reverse + * mapping, so simply use the second one. + */ + /* [0x39] = {5,2}, space */ + [0x39] = {5,3}, /* space */ + /* + * Matrix position {5,4} and other keys are missing here. + * TODO: Compare with Linux code and test real hardware. + */ + [0x1c] = {5,5}, /* enter (TODO: might be wrong) */ + [0xc8] = {6,0}, /* up */ + [0xd0] = {6,1}, /* down */ + [0xcb] = {6,2}, /* left */ + [0xcd] = {6,3}, /* right */ +}; + +enum mainstone_model_e { mainstone }; + +#define MAINSTONE_RAM 0x04000000 +#define MAINSTONE_ROM 0x00800000 +#define MAINSTONE_FLASH 0x02000000 + +static struct arm_boot_info mainstone_binfo = { + .loader_start = PXA2XX_SDRAM_BASE, + .ram_size = 0x04000000, +}; + +static void mainstone_common_init(MemoryRegion *address_space_mem, + MachineState *machine, + enum mainstone_model_e model, int arm_id) +{ + uint32_t sector_len = 256 * 1024; + hwaddr mainstone_flash_base[] = { MST_FLASH_0, MST_FLASH_1 }; + PXA2xxState *mpu; + DeviceState *mst_irq; + DriveInfo *dinfo; + int i; + int be; + MemoryRegion *rom = g_new(MemoryRegion, 1); + const char *cpu_model = machine->cpu_model; + + if (!cpu_model) + cpu_model = "pxa270-c5"; + + /* Setup CPU & memory */ + mpu = pxa270_init(address_space_mem, mainstone_binfo.ram_size, cpu_model); + memory_region_init_ram(rom, NULL, "mainstone.rom", MAINSTONE_ROM, + &error_abort); + vmstate_register_ram_global(rom); + memory_region_set_readonly(rom, true); + memory_region_add_subregion(address_space_mem, 0, rom); + +#ifdef TARGET_WORDS_BIGENDIAN + be = 1; +#else + be = 0; +#endif + /* There are two 32MiB flash devices on the board */ + for (i = 0; i < 2; i ++) { + dinfo = drive_get(IF_PFLASH, 0, i); + if (!dinfo) { + if (qtest_enabled()) { + break; + } + fprintf(stderr, "Two flash images must be given with the " + "'pflash' parameter\n"); + exit(1); + } + + if (!pflash_cfi01_register(mainstone_flash_base[i], NULL, + i ? "mainstone.flash1" : "mainstone.flash0", + MAINSTONE_FLASH, + blk_by_legacy_dinfo(dinfo), + sector_len, MAINSTONE_FLASH / sector_len, + 4, 0, 0, 0, 0, be)) { + fprintf(stderr, "qemu: Error registering flash memory.\n"); + exit(1); + } + } + + mst_irq = sysbus_create_simple("mainstone-fpga", MST_FPGA_PHYS, + qdev_get_gpio_in(mpu->gpio, 0)); + + /* setup keypad */ + pxa27x_register_keypad(mpu->kp, map, 0xe0); + + /* MMC/SD host */ + pxa2xx_mmci_handlers(mpu->mmc, NULL, qdev_get_gpio_in(mst_irq, MMC_IRQ)); + + pxa2xx_pcmcia_set_irq_cb(mpu->pcmcia[0], + qdev_get_gpio_in(mst_irq, S0_IRQ), + qdev_get_gpio_in(mst_irq, S0_CD_IRQ)); + pxa2xx_pcmcia_set_irq_cb(mpu->pcmcia[1], + qdev_get_gpio_in(mst_irq, S1_IRQ), + qdev_get_gpio_in(mst_irq, S1_CD_IRQ)); + + smc91c111_init(&nd_table[0], MST_ETH_PHYS, + qdev_get_gpio_in(mst_irq, ETHERNET_IRQ)); + + mainstone_binfo.kernel_filename = machine->kernel_filename; + mainstone_binfo.kernel_cmdline = machine->kernel_cmdline; + mainstone_binfo.initrd_filename = machine->initrd_filename; + mainstone_binfo.board_id = arm_id; + arm_load_kernel(mpu->cpu, &mainstone_binfo); +} + +static void mainstone_init(MachineState *machine) +{ + mainstone_common_init(get_system_memory(), machine, mainstone, 0x196); +} + +static QEMUMachine mainstone2_machine = { + .name = "mainstone", + .desc = "Mainstone II (PXA27x)", + .init = mainstone_init, +}; + +static void mainstone_machine_init(void) +{ + qemu_register_machine(&mainstone2_machine); +} + +machine_init(mainstone_machine_init); diff --git a/qemu/hw/arm/musicpal.c b/qemu/hw/arm/musicpal.c new file mode 100644 index 000000000..42f66b33e --- /dev/null +++ b/qemu/hw/arm/musicpal.c @@ -0,0 +1,1752 @@ +/* + * Marvell MV88W8618 / Freecom MusicPal emulation. + * + * Copyright (c) 2008 Jan Kiszka + * + * This code is licensed under the GNU GPL v2. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "hw/sysbus.h" +#include "hw/arm/arm.h" +#include "hw/devices.h" +#include "net/net.h" +#include "sysemu/sysemu.h" +#include "hw/boards.h" +#include "hw/char/serial.h" +#include "qemu/timer.h" +#include "hw/ptimer.h" +#include "hw/block/flash.h" +#include "ui/console.h" +#include "hw/i2c/i2c.h" +#include "sysemu/block-backend.h" +#include "exec/address-spaces.h" +#include "ui/pixel_ops.h" + +#define MP_MISC_BASE 0x80002000 +#define MP_MISC_SIZE 0x00001000 + +#define MP_ETH_BASE 0x80008000 +#define MP_ETH_SIZE 0x00001000 + +#define MP_WLAN_BASE 0x8000C000 +#define MP_WLAN_SIZE 0x00000800 + +#define MP_UART1_BASE 0x8000C840 +#define MP_UART2_BASE 0x8000C940 + +#define MP_GPIO_BASE 0x8000D000 +#define MP_GPIO_SIZE 0x00001000 + +#define MP_FLASHCFG_BASE 0x90006000 +#define MP_FLASHCFG_SIZE 0x00001000 + +#define MP_AUDIO_BASE 0x90007000 + +#define MP_PIC_BASE 0x90008000 +#define MP_PIC_SIZE 0x00001000 + +#define MP_PIT_BASE 0x90009000 +#define MP_PIT_SIZE 0x00001000 + +#define MP_LCD_BASE 0x9000c000 +#define MP_LCD_SIZE 0x00001000 + +#define MP_SRAM_BASE 0xC0000000 +#define MP_SRAM_SIZE 0x00020000 + +#define MP_RAM_DEFAULT_SIZE 32*1024*1024 +#define MP_FLASH_SIZE_MAX 32*1024*1024 + +#define MP_TIMER1_IRQ 4 +#define MP_TIMER2_IRQ 5 +#define MP_TIMER3_IRQ 6 +#define MP_TIMER4_IRQ 7 +#define MP_EHCI_IRQ 8 +#define MP_ETH_IRQ 9 +#define MP_UART1_IRQ 11 +#define MP_UART2_IRQ 11 +#define MP_GPIO_IRQ 12 +#define MP_RTC_IRQ 28 +#define MP_AUDIO_IRQ 30 + +/* Wolfson 8750 I2C address */ +#define MP_WM_ADDR 0x1A + +/* Ethernet register offsets */ +#define MP_ETH_SMIR 0x010 +#define MP_ETH_PCXR 0x408 +#define MP_ETH_SDCMR 0x448 +#define MP_ETH_ICR 0x450 +#define MP_ETH_IMR 0x458 +#define MP_ETH_FRDP0 0x480 +#define MP_ETH_FRDP1 0x484 +#define MP_ETH_FRDP2 0x488 +#define MP_ETH_FRDP3 0x48C +#define MP_ETH_CRDP0 0x4A0 +#define MP_ETH_CRDP1 0x4A4 +#define MP_ETH_CRDP2 0x4A8 +#define MP_ETH_CRDP3 0x4AC +#define MP_ETH_CTDP0 0x4E0 +#define MP_ETH_CTDP1 0x4E4 + +/* MII PHY access */ +#define MP_ETH_SMIR_DATA 0x0000FFFF +#define MP_ETH_SMIR_ADDR 0x03FF0000 +#define MP_ETH_SMIR_OPCODE (1 << 26) /* Read value */ +#define MP_ETH_SMIR_RDVALID (1 << 27) + +/* PHY registers */ +#define MP_ETH_PHY1_BMSR 0x00210000 +#define MP_ETH_PHY1_PHYSID1 0x00410000 +#define MP_ETH_PHY1_PHYSID2 0x00610000 + +#define MP_PHY_BMSR_LINK 0x0004 +#define MP_PHY_BMSR_AUTONEG 0x0008 + +#define MP_PHY_88E3015 0x01410E20 + +/* TX descriptor status */ +#define MP_ETH_TX_OWN (1U << 31) + +/* RX descriptor status */ +#define MP_ETH_RX_OWN (1U << 31) + +/* Interrupt cause/mask bits */ +#define MP_ETH_IRQ_RX_BIT 0 +#define MP_ETH_IRQ_RX (1 << MP_ETH_IRQ_RX_BIT) +#define MP_ETH_IRQ_TXHI_BIT 2 +#define MP_ETH_IRQ_TXLO_BIT 3 + +/* Port config bits */ +#define MP_ETH_PCXR_2BSM_BIT 28 /* 2-byte incoming suffix */ + +/* SDMA command bits */ +#define MP_ETH_CMD_TXHI (1 << 23) +#define MP_ETH_CMD_TXLO (1 << 22) + +typedef struct mv88w8618_tx_desc { + uint32_t cmdstat; + uint16_t res; + uint16_t bytes; + uint32_t buffer; + uint32_t next; +} mv88w8618_tx_desc; + +typedef struct mv88w8618_rx_desc { + uint32_t cmdstat; + uint16_t bytes; + uint16_t buffer_size; + uint32_t buffer; + uint32_t next; +} mv88w8618_rx_desc; + +#define TYPE_MV88W8618_ETH "mv88w8618_eth" +#define MV88W8618_ETH(obj) \ + OBJECT_CHECK(mv88w8618_eth_state, (obj), TYPE_MV88W8618_ETH) + +typedef struct mv88w8618_eth_state { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion iomem; + qemu_irq irq; + uint32_t smir; + uint32_t icr; + uint32_t imr; + int mmio_index; + uint32_t vlan_header; + uint32_t tx_queue[2]; + uint32_t rx_queue[4]; + uint32_t frx_queue[4]; + uint32_t cur_rx[4]; + NICState *nic; + NICConf conf; +} mv88w8618_eth_state; + +static void eth_rx_desc_put(uint32_t addr, mv88w8618_rx_desc *desc) +{ + cpu_to_le32s(&desc->cmdstat); + cpu_to_le16s(&desc->bytes); + cpu_to_le16s(&desc->buffer_size); + cpu_to_le32s(&desc->buffer); + cpu_to_le32s(&desc->next); + cpu_physical_memory_write(addr, desc, sizeof(*desc)); +} + +static void eth_rx_desc_get(uint32_t addr, mv88w8618_rx_desc *desc) +{ + cpu_physical_memory_read(addr, desc, sizeof(*desc)); + le32_to_cpus(&desc->cmdstat); + le16_to_cpus(&desc->bytes); + le16_to_cpus(&desc->buffer_size); + le32_to_cpus(&desc->buffer); + le32_to_cpus(&desc->next); +} + +static ssize_t eth_receive(NetClientState *nc, const uint8_t *buf, size_t size) +{ + mv88w8618_eth_state *s = qemu_get_nic_opaque(nc); + uint32_t desc_addr; + mv88w8618_rx_desc desc; + int i; + + for (i = 0; i < 4; i++) { + desc_addr = s->cur_rx[i]; + if (!desc_addr) { + continue; + } + do { + eth_rx_desc_get(desc_addr, &desc); + if ((desc.cmdstat & MP_ETH_RX_OWN) && desc.buffer_size >= size) { + cpu_physical_memory_write(desc.buffer + s->vlan_header, + buf, size); + desc.bytes = size + s->vlan_header; + desc.cmdstat &= ~MP_ETH_RX_OWN; + s->cur_rx[i] = desc.next; + + s->icr |= MP_ETH_IRQ_RX; + if (s->icr & s->imr) { + qemu_irq_raise(s->irq); + } + eth_rx_desc_put(desc_addr, &desc); + return size; + } + desc_addr = desc.next; + } while (desc_addr != s->rx_queue[i]); + } + return size; +} + +static void eth_tx_desc_put(uint32_t addr, mv88w8618_tx_desc *desc) +{ + cpu_to_le32s(&desc->cmdstat); + cpu_to_le16s(&desc->res); + cpu_to_le16s(&desc->bytes); + cpu_to_le32s(&desc->buffer); + cpu_to_le32s(&desc->next); + cpu_physical_memory_write(addr, desc, sizeof(*desc)); +} + +static void eth_tx_desc_get(uint32_t addr, mv88w8618_tx_desc *desc) +{ + cpu_physical_memory_read(addr, desc, sizeof(*desc)); + le32_to_cpus(&desc->cmdstat); + le16_to_cpus(&desc->res); + le16_to_cpus(&desc->bytes); + le32_to_cpus(&desc->buffer); + le32_to_cpus(&desc->next); +} + +static void eth_send(mv88w8618_eth_state *s, int queue_index) +{ + uint32_t desc_addr = s->tx_queue[queue_index]; + mv88w8618_tx_desc desc; + uint32_t next_desc; + uint8_t buf[2048]; + int len; + + do { + eth_tx_desc_get(desc_addr, &desc); + next_desc = desc.next; + if (desc.cmdstat & MP_ETH_TX_OWN) { + len = desc.bytes; + if (len < 2048) { + cpu_physical_memory_read(desc.buffer, buf, len); + qemu_send_packet(qemu_get_queue(s->nic), buf, len); + } + desc.cmdstat &= ~MP_ETH_TX_OWN; + s->icr |= 1 << (MP_ETH_IRQ_TXLO_BIT - queue_index); + eth_tx_desc_put(desc_addr, &desc); + } + desc_addr = next_desc; + } while (desc_addr != s->tx_queue[queue_index]); +} + +static uint64_t mv88w8618_eth_read(void *opaque, hwaddr offset, + unsigned size) +{ + mv88w8618_eth_state *s = opaque; + + switch (offset) { + case MP_ETH_SMIR: + if (s->smir & MP_ETH_SMIR_OPCODE) { + switch (s->smir & MP_ETH_SMIR_ADDR) { + case MP_ETH_PHY1_BMSR: + return MP_PHY_BMSR_LINK | MP_PHY_BMSR_AUTONEG | + MP_ETH_SMIR_RDVALID; + case MP_ETH_PHY1_PHYSID1: + return (MP_PHY_88E3015 >> 16) | MP_ETH_SMIR_RDVALID; + case MP_ETH_PHY1_PHYSID2: + return (MP_PHY_88E3015 & 0xFFFF) | MP_ETH_SMIR_RDVALID; + default: + return MP_ETH_SMIR_RDVALID; + } + } + return 0; + + case MP_ETH_ICR: + return s->icr; + + case MP_ETH_IMR: + return s->imr; + + case MP_ETH_FRDP0 ... MP_ETH_FRDP3: + return s->frx_queue[(offset - MP_ETH_FRDP0)/4]; + + case MP_ETH_CRDP0 ... MP_ETH_CRDP3: + return s->rx_queue[(offset - MP_ETH_CRDP0)/4]; + + case MP_ETH_CTDP0 ... MP_ETH_CTDP1: + return s->tx_queue[(offset - MP_ETH_CTDP0)/4]; + + default: + return 0; + } +} + +static void mv88w8618_eth_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + mv88w8618_eth_state *s = opaque; + + switch (offset) { + case MP_ETH_SMIR: + s->smir = value; + break; + + case MP_ETH_PCXR: + s->vlan_header = ((value >> MP_ETH_PCXR_2BSM_BIT) & 1) * 2; + break; + + case MP_ETH_SDCMR: + if (value & MP_ETH_CMD_TXHI) { + eth_send(s, 1); + } + if (value & MP_ETH_CMD_TXLO) { + eth_send(s, 0); + } + if (value & (MP_ETH_CMD_TXHI | MP_ETH_CMD_TXLO) && s->icr & s->imr) { + qemu_irq_raise(s->irq); + } + break; + + case MP_ETH_ICR: + s->icr &= value; + break; + + case MP_ETH_IMR: + s->imr = value; + if (s->icr & s->imr) { + qemu_irq_raise(s->irq); + } + break; + + case MP_ETH_FRDP0 ... MP_ETH_FRDP3: + s->frx_queue[(offset - MP_ETH_FRDP0)/4] = value; + break; + + case MP_ETH_CRDP0 ... MP_ETH_CRDP3: + s->rx_queue[(offset - MP_ETH_CRDP0)/4] = + s->cur_rx[(offset - MP_ETH_CRDP0)/4] = value; + break; + + case MP_ETH_CTDP0 ... MP_ETH_CTDP1: + s->tx_queue[(offset - MP_ETH_CTDP0)/4] = value; + break; + } +} + +static const MemoryRegionOps mv88w8618_eth_ops = { + .read = mv88w8618_eth_read, + .write = mv88w8618_eth_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void eth_cleanup(NetClientState *nc) +{ + mv88w8618_eth_state *s = qemu_get_nic_opaque(nc); + + s->nic = NULL; +} + +static NetClientInfo net_mv88w8618_info = { + .type = NET_CLIENT_OPTIONS_KIND_NIC, + .size = sizeof(NICState), + .receive = eth_receive, + .cleanup = eth_cleanup, +}; + +static int mv88w8618_eth_init(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + mv88w8618_eth_state *s = MV88W8618_ETH(dev); + + sysbus_init_irq(sbd, &s->irq); + s->nic = qemu_new_nic(&net_mv88w8618_info, &s->conf, + object_get_typename(OBJECT(dev)), dev->id, s); + memory_region_init_io(&s->iomem, OBJECT(s), &mv88w8618_eth_ops, s, + "mv88w8618-eth", MP_ETH_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + return 0; +} + +static const VMStateDescription mv88w8618_eth_vmsd = { + .name = "mv88w8618_eth", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(smir, mv88w8618_eth_state), + VMSTATE_UINT32(icr, mv88w8618_eth_state), + VMSTATE_UINT32(imr, mv88w8618_eth_state), + VMSTATE_UINT32(vlan_header, mv88w8618_eth_state), + VMSTATE_UINT32_ARRAY(tx_queue, mv88w8618_eth_state, 2), + VMSTATE_UINT32_ARRAY(rx_queue, mv88w8618_eth_state, 4), + VMSTATE_UINT32_ARRAY(frx_queue, mv88w8618_eth_state, 4), + VMSTATE_UINT32_ARRAY(cur_rx, mv88w8618_eth_state, 4), + VMSTATE_END_OF_LIST() + } +}; + +static Property mv88w8618_eth_properties[] = { + DEFINE_NIC_PROPERTIES(mv88w8618_eth_state, conf), + DEFINE_PROP_END_OF_LIST(), +}; + +static void mv88w8618_eth_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = mv88w8618_eth_init; + dc->vmsd = &mv88w8618_eth_vmsd; + dc->props = mv88w8618_eth_properties; +} + +static const TypeInfo mv88w8618_eth_info = { + .name = TYPE_MV88W8618_ETH, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(mv88w8618_eth_state), + .class_init = mv88w8618_eth_class_init, +}; + +/* LCD register offsets */ +#define MP_LCD_IRQCTRL 0x180 +#define MP_LCD_IRQSTAT 0x184 +#define MP_LCD_SPICTRL 0x1ac +#define MP_LCD_INST 0x1bc +#define MP_LCD_DATA 0x1c0 + +/* Mode magics */ +#define MP_LCD_SPI_DATA 0x00100011 +#define MP_LCD_SPI_CMD 0x00104011 +#define MP_LCD_SPI_INVALID 0x00000000 + +/* Commmands */ +#define MP_LCD_INST_SETPAGE0 0xB0 +/* ... */ +#define MP_LCD_INST_SETPAGE7 0xB7 + +#define MP_LCD_TEXTCOLOR 0xe0e0ff /* RRGGBB */ + +#define TYPE_MUSICPAL_LCD "musicpal_lcd" +#define MUSICPAL_LCD(obj) \ + OBJECT_CHECK(musicpal_lcd_state, (obj), TYPE_MUSICPAL_LCD) + +typedef struct musicpal_lcd_state { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion iomem; + uint32_t brightness; + uint32_t mode; + uint32_t irqctrl; + uint32_t page; + uint32_t page_off; + QemuConsole *con; + uint8_t video_ram[128*64/8]; +} musicpal_lcd_state; + +static uint8_t scale_lcd_color(musicpal_lcd_state *s, uint8_t col) +{ + switch (s->brightness) { + case 7: + return col; + case 0: + return 0; + default: + return (col * s->brightness) / 7; + } +} + +#define SET_LCD_PIXEL(depth, type) \ +static inline void glue(set_lcd_pixel, depth) \ + (musicpal_lcd_state *s, int x, int y, type col) \ +{ \ + int dx, dy; \ + DisplaySurface *surface = qemu_console_surface(s->con); \ + type *pixel = &((type *) surface_data(surface))[(y * 128 * 3 + x) * 3]; \ +\ + for (dy = 0; dy < 3; dy++, pixel += 127 * 3) \ + for (dx = 0; dx < 3; dx++, pixel++) \ + *pixel = col; \ +} +SET_LCD_PIXEL(8, uint8_t) +SET_LCD_PIXEL(16, uint16_t) +SET_LCD_PIXEL(32, uint32_t) + +static void lcd_refresh(void *opaque) +{ + musicpal_lcd_state *s = opaque; + DisplaySurface *surface = qemu_console_surface(s->con); + int x, y, col; + + switch (surface_bits_per_pixel(surface)) { + case 0: + return; +#define LCD_REFRESH(depth, func) \ + case depth: \ + col = func(scale_lcd_color(s, (MP_LCD_TEXTCOLOR >> 16) & 0xff), \ + scale_lcd_color(s, (MP_LCD_TEXTCOLOR >> 8) & 0xff), \ + scale_lcd_color(s, MP_LCD_TEXTCOLOR & 0xff)); \ + for (x = 0; x < 128; x++) { \ + for (y = 0; y < 64; y++) { \ + if (s->video_ram[x + (y/8)*128] & (1 << (y % 8))) { \ + glue(set_lcd_pixel, depth)(s, x, y, col); \ + } else { \ + glue(set_lcd_pixel, depth)(s, x, y, 0); \ + } \ + } \ + } \ + break; + LCD_REFRESH(8, rgb_to_pixel8) + LCD_REFRESH(16, rgb_to_pixel16) + LCD_REFRESH(32, (is_surface_bgr(surface) ? + rgb_to_pixel32bgr : rgb_to_pixel32)) + default: + hw_error("unsupported colour depth %i\n", + surface_bits_per_pixel(surface)); + } + + dpy_gfx_update(s->con, 0, 0, 128*3, 64*3); +} + +static void lcd_invalidate(void *opaque) +{ +} + +static void musicpal_lcd_gpio_brightness_in(void *opaque, int irq, int level) +{ + musicpal_lcd_state *s = opaque; + s->brightness &= ~(1 << irq); + s->brightness |= level << irq; +} + +static uint64_t musicpal_lcd_read(void *opaque, hwaddr offset, + unsigned size) +{ + musicpal_lcd_state *s = opaque; + + switch (offset) { + case MP_LCD_IRQCTRL: + return s->irqctrl; + + default: + return 0; + } +} + +static void musicpal_lcd_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + musicpal_lcd_state *s = opaque; + + switch (offset) { + case MP_LCD_IRQCTRL: + s->irqctrl = value; + break; + + case MP_LCD_SPICTRL: + if (value == MP_LCD_SPI_DATA || value == MP_LCD_SPI_CMD) { + s->mode = value; + } else { + s->mode = MP_LCD_SPI_INVALID; + } + break; + + case MP_LCD_INST: + if (value >= MP_LCD_INST_SETPAGE0 && value <= MP_LCD_INST_SETPAGE7) { + s->page = value - MP_LCD_INST_SETPAGE0; + s->page_off = 0; + } + break; + + case MP_LCD_DATA: + if (s->mode == MP_LCD_SPI_CMD) { + if (value >= MP_LCD_INST_SETPAGE0 && + value <= MP_LCD_INST_SETPAGE7) { + s->page = value - MP_LCD_INST_SETPAGE0; + s->page_off = 0; + } + } else if (s->mode == MP_LCD_SPI_DATA) { + s->video_ram[s->page*128 + s->page_off] = value; + s->page_off = (s->page_off + 1) & 127; + } + break; + } +} + +static const MemoryRegionOps musicpal_lcd_ops = { + .read = musicpal_lcd_read, + .write = musicpal_lcd_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const GraphicHwOps musicpal_gfx_ops = { + .invalidate = lcd_invalidate, + .gfx_update = lcd_refresh, +}; + +static int musicpal_lcd_init(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + musicpal_lcd_state *s = MUSICPAL_LCD(dev); + + s->brightness = 7; + + memory_region_init_io(&s->iomem, OBJECT(s), &musicpal_lcd_ops, s, + "musicpal-lcd", MP_LCD_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + + s->con = graphic_console_init(dev, 0, &musicpal_gfx_ops, s); + qemu_console_resize(s->con, 128*3, 64*3); + + qdev_init_gpio_in(dev, musicpal_lcd_gpio_brightness_in, 3); + + return 0; +} + +static const VMStateDescription musicpal_lcd_vmsd = { + .name = "musicpal_lcd", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(brightness, musicpal_lcd_state), + VMSTATE_UINT32(mode, musicpal_lcd_state), + VMSTATE_UINT32(irqctrl, musicpal_lcd_state), + VMSTATE_UINT32(page, musicpal_lcd_state), + VMSTATE_UINT32(page_off, musicpal_lcd_state), + VMSTATE_BUFFER(video_ram, musicpal_lcd_state), + VMSTATE_END_OF_LIST() + } +}; + +static void musicpal_lcd_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = musicpal_lcd_init; + dc->vmsd = &musicpal_lcd_vmsd; +} + +static const TypeInfo musicpal_lcd_info = { + .name = TYPE_MUSICPAL_LCD, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(musicpal_lcd_state), + .class_init = musicpal_lcd_class_init, +}; + +/* PIC register offsets */ +#define MP_PIC_STATUS 0x00 +#define MP_PIC_ENABLE_SET 0x08 +#define MP_PIC_ENABLE_CLR 0x0C + +#define TYPE_MV88W8618_PIC "mv88w8618_pic" +#define MV88W8618_PIC(obj) \ + OBJECT_CHECK(mv88w8618_pic_state, (obj), TYPE_MV88W8618_PIC) + +typedef struct mv88w8618_pic_state { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion iomem; + uint32_t level; + uint32_t enabled; + qemu_irq parent_irq; +} mv88w8618_pic_state; + +static void mv88w8618_pic_update(mv88w8618_pic_state *s) +{ + qemu_set_irq(s->parent_irq, (s->level & s->enabled)); +} + +static void mv88w8618_pic_set_irq(void *opaque, int irq, int level) +{ + mv88w8618_pic_state *s = opaque; + + if (level) { + s->level |= 1 << irq; + } else { + s->level &= ~(1 << irq); + } + mv88w8618_pic_update(s); +} + +static uint64_t mv88w8618_pic_read(void *opaque, hwaddr offset, + unsigned size) +{ + mv88w8618_pic_state *s = opaque; + + switch (offset) { + case MP_PIC_STATUS: + return s->level & s->enabled; + + default: + return 0; + } +} + +static void mv88w8618_pic_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + mv88w8618_pic_state *s = opaque; + + switch (offset) { + case MP_PIC_ENABLE_SET: + s->enabled |= value; + break; + + case MP_PIC_ENABLE_CLR: + s->enabled &= ~value; + s->level &= ~value; + break; + } + mv88w8618_pic_update(s); +} + +static void mv88w8618_pic_reset(DeviceState *d) +{ + mv88w8618_pic_state *s = MV88W8618_PIC(d); + + s->level = 0; + s->enabled = 0; +} + +static const MemoryRegionOps mv88w8618_pic_ops = { + .read = mv88w8618_pic_read, + .write = mv88w8618_pic_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int mv88w8618_pic_init(SysBusDevice *dev) +{ + mv88w8618_pic_state *s = MV88W8618_PIC(dev); + + qdev_init_gpio_in(DEVICE(dev), mv88w8618_pic_set_irq, 32); + sysbus_init_irq(dev, &s->parent_irq); + memory_region_init_io(&s->iomem, OBJECT(s), &mv88w8618_pic_ops, s, + "musicpal-pic", MP_PIC_SIZE); + sysbus_init_mmio(dev, &s->iomem); + return 0; +} + +static const VMStateDescription mv88w8618_pic_vmsd = { + .name = "mv88w8618_pic", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(level, mv88w8618_pic_state), + VMSTATE_UINT32(enabled, mv88w8618_pic_state), + VMSTATE_END_OF_LIST() + } +}; + +static void mv88w8618_pic_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = mv88w8618_pic_init; + dc->reset = mv88w8618_pic_reset; + dc->vmsd = &mv88w8618_pic_vmsd; +} + +static const TypeInfo mv88w8618_pic_info = { + .name = TYPE_MV88W8618_PIC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(mv88w8618_pic_state), + .class_init = mv88w8618_pic_class_init, +}; + +/* PIT register offsets */ +#define MP_PIT_TIMER1_LENGTH 0x00 +/* ... */ +#define MP_PIT_TIMER4_LENGTH 0x0C +#define MP_PIT_CONTROL 0x10 +#define MP_PIT_TIMER1_VALUE 0x14 +/* ... */ +#define MP_PIT_TIMER4_VALUE 0x20 +#define MP_BOARD_RESET 0x34 + +/* Magic board reset value (probably some watchdog behind it) */ +#define MP_BOARD_RESET_MAGIC 0x10000 + +typedef struct mv88w8618_timer_state { + ptimer_state *ptimer; + uint32_t limit; + int freq; + qemu_irq irq; +} mv88w8618_timer_state; + +#define TYPE_MV88W8618_PIT "mv88w8618_pit" +#define MV88W8618_PIT(obj) \ + OBJECT_CHECK(mv88w8618_pit_state, (obj), TYPE_MV88W8618_PIT) + +typedef struct mv88w8618_pit_state { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion iomem; + mv88w8618_timer_state timer[4]; +} mv88w8618_pit_state; + +static void mv88w8618_timer_tick(void *opaque) +{ + mv88w8618_timer_state *s = opaque; + + qemu_irq_raise(s->irq); +} + +static void mv88w8618_timer_init(SysBusDevice *dev, mv88w8618_timer_state *s, + uint32_t freq) +{ + QEMUBH *bh; + + sysbus_init_irq(dev, &s->irq); + s->freq = freq; + + bh = qemu_bh_new(mv88w8618_timer_tick, s); + s->ptimer = ptimer_init(bh); +} + +static uint64_t mv88w8618_pit_read(void *opaque, hwaddr offset, + unsigned size) +{ + mv88w8618_pit_state *s = opaque; + mv88w8618_timer_state *t; + + switch (offset) { + case MP_PIT_TIMER1_VALUE ... MP_PIT_TIMER4_VALUE: + t = &s->timer[(offset-MP_PIT_TIMER1_VALUE) >> 2]; + return ptimer_get_count(t->ptimer); + + default: + return 0; + } +} + +static void mv88w8618_pit_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + mv88w8618_pit_state *s = opaque; + mv88w8618_timer_state *t; + int i; + + switch (offset) { + case MP_PIT_TIMER1_LENGTH ... MP_PIT_TIMER4_LENGTH: + t = &s->timer[offset >> 2]; + t->limit = value; + if (t->limit > 0) { + ptimer_set_limit(t->ptimer, t->limit, 1); + } else { + ptimer_stop(t->ptimer); + } + break; + + case MP_PIT_CONTROL: + for (i = 0; i < 4; i++) { + t = &s->timer[i]; + if (value & 0xf && t->limit > 0) { + ptimer_set_limit(t->ptimer, t->limit, 0); + ptimer_set_freq(t->ptimer, t->freq); + ptimer_run(t->ptimer, 0); + } else { + ptimer_stop(t->ptimer); + } + value >>= 4; + } + break; + + case MP_BOARD_RESET: + if (value == MP_BOARD_RESET_MAGIC) { + qemu_system_reset_request(); + } + break; + } +} + +static void mv88w8618_pit_reset(DeviceState *d) +{ + mv88w8618_pit_state *s = MV88W8618_PIT(d); + int i; + + for (i = 0; i < 4; i++) { + ptimer_stop(s->timer[i].ptimer); + s->timer[i].limit = 0; + } +} + +static const MemoryRegionOps mv88w8618_pit_ops = { + .read = mv88w8618_pit_read, + .write = mv88w8618_pit_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int mv88w8618_pit_init(SysBusDevice *dev) +{ + mv88w8618_pit_state *s = MV88W8618_PIT(dev); + int i; + + /* Letting them all run at 1 MHz is likely just a pragmatic + * simplification. */ + for (i = 0; i < 4; i++) { + mv88w8618_timer_init(dev, &s->timer[i], 1000000); + } + + memory_region_init_io(&s->iomem, OBJECT(s), &mv88w8618_pit_ops, s, + "musicpal-pit", MP_PIT_SIZE); + sysbus_init_mmio(dev, &s->iomem); + return 0; +} + +static const VMStateDescription mv88w8618_timer_vmsd = { + .name = "timer", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_PTIMER(ptimer, mv88w8618_timer_state), + VMSTATE_UINT32(limit, mv88w8618_timer_state), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription mv88w8618_pit_vmsd = { + .name = "mv88w8618_pit", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT_ARRAY(timer, mv88w8618_pit_state, 4, 1, + mv88w8618_timer_vmsd, mv88w8618_timer_state), + VMSTATE_END_OF_LIST() + } +}; + +static void mv88w8618_pit_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = mv88w8618_pit_init; + dc->reset = mv88w8618_pit_reset; + dc->vmsd = &mv88w8618_pit_vmsd; +} + +static const TypeInfo mv88w8618_pit_info = { + .name = TYPE_MV88W8618_PIT, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(mv88w8618_pit_state), + .class_init = mv88w8618_pit_class_init, +}; + +/* Flash config register offsets */ +#define MP_FLASHCFG_CFGR0 0x04 + +#define TYPE_MV88W8618_FLASHCFG "mv88w8618_flashcfg" +#define MV88W8618_FLASHCFG(obj) \ + OBJECT_CHECK(mv88w8618_flashcfg_state, (obj), TYPE_MV88W8618_FLASHCFG) + +typedef struct mv88w8618_flashcfg_state { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion iomem; + uint32_t cfgr0; +} mv88w8618_flashcfg_state; + +static uint64_t mv88w8618_flashcfg_read(void *opaque, + hwaddr offset, + unsigned size) +{ + mv88w8618_flashcfg_state *s = opaque; + + switch (offset) { + case MP_FLASHCFG_CFGR0: + return s->cfgr0; + + default: + return 0; + } +} + +static void mv88w8618_flashcfg_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + mv88w8618_flashcfg_state *s = opaque; + + switch (offset) { + case MP_FLASHCFG_CFGR0: + s->cfgr0 = value; + break; + } +} + +static const MemoryRegionOps mv88w8618_flashcfg_ops = { + .read = mv88w8618_flashcfg_read, + .write = mv88w8618_flashcfg_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int mv88w8618_flashcfg_init(SysBusDevice *dev) +{ + mv88w8618_flashcfg_state *s = MV88W8618_FLASHCFG(dev); + + s->cfgr0 = 0xfffe4285; /* Default as set by U-Boot for 8 MB flash */ + memory_region_init_io(&s->iomem, OBJECT(s), &mv88w8618_flashcfg_ops, s, + "musicpal-flashcfg", MP_FLASHCFG_SIZE); + sysbus_init_mmio(dev, &s->iomem); + return 0; +} + +static const VMStateDescription mv88w8618_flashcfg_vmsd = { + .name = "mv88w8618_flashcfg", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(cfgr0, mv88w8618_flashcfg_state), + VMSTATE_END_OF_LIST() + } +}; + +static void mv88w8618_flashcfg_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = mv88w8618_flashcfg_init; + dc->vmsd = &mv88w8618_flashcfg_vmsd; +} + +static const TypeInfo mv88w8618_flashcfg_info = { + .name = TYPE_MV88W8618_FLASHCFG, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(mv88w8618_flashcfg_state), + .class_init = mv88w8618_flashcfg_class_init, +}; + +/* Misc register offsets */ +#define MP_MISC_BOARD_REVISION 0x18 + +#define MP_BOARD_REVISION 0x31 + +typedef struct { + SysBusDevice parent_obj; + MemoryRegion iomem; +} MusicPalMiscState; + +#define TYPE_MUSICPAL_MISC "musicpal-misc" +#define MUSICPAL_MISC(obj) \ + OBJECT_CHECK(MusicPalMiscState, (obj), TYPE_MUSICPAL_MISC) + +static uint64_t musicpal_misc_read(void *opaque, hwaddr offset, + unsigned size) +{ + switch (offset) { + case MP_MISC_BOARD_REVISION: + return MP_BOARD_REVISION; + + default: + return 0; + } +} + +static void musicpal_misc_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ +} + +static const MemoryRegionOps musicpal_misc_ops = { + .read = musicpal_misc_read, + .write = musicpal_misc_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void musicpal_misc_init(Object *obj) +{ + SysBusDevice *sd = SYS_BUS_DEVICE(obj); + MusicPalMiscState *s = MUSICPAL_MISC(obj); + + memory_region_init_io(&s->iomem, OBJECT(s), &musicpal_misc_ops, NULL, + "musicpal-misc", MP_MISC_SIZE); + sysbus_init_mmio(sd, &s->iomem); +} + +static const TypeInfo musicpal_misc_info = { + .name = TYPE_MUSICPAL_MISC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_init = musicpal_misc_init, + .instance_size = sizeof(MusicPalMiscState), +}; + +/* WLAN register offsets */ +#define MP_WLAN_MAGIC1 0x11c +#define MP_WLAN_MAGIC2 0x124 + +static uint64_t mv88w8618_wlan_read(void *opaque, hwaddr offset, + unsigned size) +{ + switch (offset) { + /* Workaround to allow loading the binary-only wlandrv.ko crap + * from the original Freecom firmware. */ + case MP_WLAN_MAGIC1: + return ~3; + case MP_WLAN_MAGIC2: + return -1; + + default: + return 0; + } +} + +static void mv88w8618_wlan_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ +} + +static const MemoryRegionOps mv88w8618_wlan_ops = { + .read = mv88w8618_wlan_read, + .write =mv88w8618_wlan_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int mv88w8618_wlan_init(SysBusDevice *dev) +{ + MemoryRegion *iomem = g_new(MemoryRegion, 1); + + memory_region_init_io(iomem, OBJECT(dev), &mv88w8618_wlan_ops, NULL, + "musicpal-wlan", MP_WLAN_SIZE); + sysbus_init_mmio(dev, iomem); + return 0; +} + +/* GPIO register offsets */ +#define MP_GPIO_OE_LO 0x008 +#define MP_GPIO_OUT_LO 0x00c +#define MP_GPIO_IN_LO 0x010 +#define MP_GPIO_IER_LO 0x014 +#define MP_GPIO_IMR_LO 0x018 +#define MP_GPIO_ISR_LO 0x020 +#define MP_GPIO_OE_HI 0x508 +#define MP_GPIO_OUT_HI 0x50c +#define MP_GPIO_IN_HI 0x510 +#define MP_GPIO_IER_HI 0x514 +#define MP_GPIO_IMR_HI 0x518 +#define MP_GPIO_ISR_HI 0x520 + +/* GPIO bits & masks */ +#define MP_GPIO_LCD_BRIGHTNESS 0x00070000 +#define MP_GPIO_I2C_DATA_BIT 29 +#define MP_GPIO_I2C_CLOCK_BIT 30 + +/* LCD brightness bits in GPIO_OE_HI */ +#define MP_OE_LCD_BRIGHTNESS 0x0007 + +#define TYPE_MUSICPAL_GPIO "musicpal_gpio" +#define MUSICPAL_GPIO(obj) \ + OBJECT_CHECK(musicpal_gpio_state, (obj), TYPE_MUSICPAL_GPIO) + +typedef struct musicpal_gpio_state { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion iomem; + uint32_t lcd_brightness; + uint32_t out_state; + uint32_t in_state; + uint32_t ier; + uint32_t imr; + uint32_t isr; + qemu_irq irq; + qemu_irq out[5]; /* 3 brightness out + 2 lcd (data and clock ) */ +} musicpal_gpio_state; + +static void musicpal_gpio_brightness_update(musicpal_gpio_state *s) { + int i; + uint32_t brightness; + + /* compute brightness ratio */ + switch (s->lcd_brightness) { + case 0x00000007: + brightness = 0; + break; + + case 0x00020000: + brightness = 1; + break; + + case 0x00020001: + brightness = 2; + break; + + case 0x00040000: + brightness = 3; + break; + + case 0x00010006: + brightness = 4; + break; + + case 0x00020005: + brightness = 5; + break; + + case 0x00040003: + brightness = 6; + break; + + case 0x00030004: + default: + brightness = 7; + } + + /* set lcd brightness GPIOs */ + for (i = 0; i <= 2; i++) { + qemu_set_irq(s->out[i], (brightness >> i) & 1); + } +} + +static void musicpal_gpio_pin_event(void *opaque, int pin, int level) +{ + musicpal_gpio_state *s = opaque; + uint32_t mask = 1 << pin; + uint32_t delta = level << pin; + uint32_t old = s->in_state & mask; + + s->in_state &= ~mask; + s->in_state |= delta; + + if ((old ^ delta) && + ((level && (s->imr & mask)) || (!level && (s->ier & mask)))) { + s->isr = mask; + qemu_irq_raise(s->irq); + } +} + +static uint64_t musicpal_gpio_read(void *opaque, hwaddr offset, + unsigned size) +{ + musicpal_gpio_state *s = opaque; + + switch (offset) { + case MP_GPIO_OE_HI: /* used for LCD brightness control */ + return s->lcd_brightness & MP_OE_LCD_BRIGHTNESS; + + case MP_GPIO_OUT_LO: + return s->out_state & 0xFFFF; + case MP_GPIO_OUT_HI: + return s->out_state >> 16; + + case MP_GPIO_IN_LO: + return s->in_state & 0xFFFF; + case MP_GPIO_IN_HI: + return s->in_state >> 16; + + case MP_GPIO_IER_LO: + return s->ier & 0xFFFF; + case MP_GPIO_IER_HI: + return s->ier >> 16; + + case MP_GPIO_IMR_LO: + return s->imr & 0xFFFF; + case MP_GPIO_IMR_HI: + return s->imr >> 16; + + case MP_GPIO_ISR_LO: + return s->isr & 0xFFFF; + case MP_GPIO_ISR_HI: + return s->isr >> 16; + + default: + return 0; + } +} + +static void musicpal_gpio_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + musicpal_gpio_state *s = opaque; + switch (offset) { + case MP_GPIO_OE_HI: /* used for LCD brightness control */ + s->lcd_brightness = (s->lcd_brightness & MP_GPIO_LCD_BRIGHTNESS) | + (value & MP_OE_LCD_BRIGHTNESS); + musicpal_gpio_brightness_update(s); + break; + + case MP_GPIO_OUT_LO: + s->out_state = (s->out_state & 0xFFFF0000) | (value & 0xFFFF); + break; + case MP_GPIO_OUT_HI: + s->out_state = (s->out_state & 0xFFFF) | (value << 16); + s->lcd_brightness = (s->lcd_brightness & 0xFFFF) | + (s->out_state & MP_GPIO_LCD_BRIGHTNESS); + musicpal_gpio_brightness_update(s); + qemu_set_irq(s->out[3], (s->out_state >> MP_GPIO_I2C_DATA_BIT) & 1); + qemu_set_irq(s->out[4], (s->out_state >> MP_GPIO_I2C_CLOCK_BIT) & 1); + break; + + case MP_GPIO_IER_LO: + s->ier = (s->ier & 0xFFFF0000) | (value & 0xFFFF); + break; + case MP_GPIO_IER_HI: + s->ier = (s->ier & 0xFFFF) | (value << 16); + break; + + case MP_GPIO_IMR_LO: + s->imr = (s->imr & 0xFFFF0000) | (value & 0xFFFF); + break; + case MP_GPIO_IMR_HI: + s->imr = (s->imr & 0xFFFF) | (value << 16); + break; + } +} + +static const MemoryRegionOps musicpal_gpio_ops = { + .read = musicpal_gpio_read, + .write = musicpal_gpio_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void musicpal_gpio_reset(DeviceState *d) +{ + musicpal_gpio_state *s = MUSICPAL_GPIO(d); + + s->lcd_brightness = 0; + s->out_state = 0; + s->in_state = 0xffffffff; + s->ier = 0; + s->imr = 0; + s->isr = 0; +} + +static int musicpal_gpio_init(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + musicpal_gpio_state *s = MUSICPAL_GPIO(dev); + + sysbus_init_irq(sbd, &s->irq); + + memory_region_init_io(&s->iomem, OBJECT(s), &musicpal_gpio_ops, s, + "musicpal-gpio", MP_GPIO_SIZE); + sysbus_init_mmio(sbd, &s->iomem); + + qdev_init_gpio_out(dev, s->out, ARRAY_SIZE(s->out)); + + qdev_init_gpio_in(dev, musicpal_gpio_pin_event, 32); + + return 0; +} + +static const VMStateDescription musicpal_gpio_vmsd = { + .name = "musicpal_gpio", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(lcd_brightness, musicpal_gpio_state), + VMSTATE_UINT32(out_state, musicpal_gpio_state), + VMSTATE_UINT32(in_state, musicpal_gpio_state), + VMSTATE_UINT32(ier, musicpal_gpio_state), + VMSTATE_UINT32(imr, musicpal_gpio_state), + VMSTATE_UINT32(isr, musicpal_gpio_state), + VMSTATE_END_OF_LIST() + } +}; + +static void musicpal_gpio_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = musicpal_gpio_init; + dc->reset = musicpal_gpio_reset; + dc->vmsd = &musicpal_gpio_vmsd; +} + +static const TypeInfo musicpal_gpio_info = { + .name = TYPE_MUSICPAL_GPIO, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(musicpal_gpio_state), + .class_init = musicpal_gpio_class_init, +}; + +/* Keyboard codes & masks */ +#define KEY_RELEASED 0x80 +#define KEY_CODE 0x7f + +#define KEYCODE_TAB 0x0f +#define KEYCODE_ENTER 0x1c +#define KEYCODE_F 0x21 +#define KEYCODE_M 0x32 + +#define KEYCODE_EXTENDED 0xe0 +#define KEYCODE_UP 0x48 +#define KEYCODE_DOWN 0x50 +#define KEYCODE_LEFT 0x4b +#define KEYCODE_RIGHT 0x4d + +#define MP_KEY_WHEEL_VOL (1 << 0) +#define MP_KEY_WHEEL_VOL_INV (1 << 1) +#define MP_KEY_WHEEL_NAV (1 << 2) +#define MP_KEY_WHEEL_NAV_INV (1 << 3) +#define MP_KEY_BTN_FAVORITS (1 << 4) +#define MP_KEY_BTN_MENU (1 << 5) +#define MP_KEY_BTN_VOLUME (1 << 6) +#define MP_KEY_BTN_NAVIGATION (1 << 7) + +#define TYPE_MUSICPAL_KEY "musicpal_key" +#define MUSICPAL_KEY(obj) \ + OBJECT_CHECK(musicpal_key_state, (obj), TYPE_MUSICPAL_KEY) + +typedef struct musicpal_key_state { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion iomem; + uint32_t kbd_extended; + uint32_t pressed_keys; + qemu_irq out[8]; +} musicpal_key_state; + +static void musicpal_key_event(void *opaque, int keycode) +{ + musicpal_key_state *s = opaque; + uint32_t event = 0; + int i; + + if (keycode == KEYCODE_EXTENDED) { + s->kbd_extended = 1; + return; + } + + if (s->kbd_extended) { + switch (keycode & KEY_CODE) { + case KEYCODE_UP: + event = MP_KEY_WHEEL_NAV | MP_KEY_WHEEL_NAV_INV; + break; + + case KEYCODE_DOWN: + event = MP_KEY_WHEEL_NAV; + break; + + case KEYCODE_LEFT: + event = MP_KEY_WHEEL_VOL | MP_KEY_WHEEL_VOL_INV; + break; + + case KEYCODE_RIGHT: + event = MP_KEY_WHEEL_VOL; + break; + } + } else { + switch (keycode & KEY_CODE) { + case KEYCODE_F: + event = MP_KEY_BTN_FAVORITS; + break; + + case KEYCODE_TAB: + event = MP_KEY_BTN_VOLUME; + break; + + case KEYCODE_ENTER: + event = MP_KEY_BTN_NAVIGATION; + break; + + case KEYCODE_M: + event = MP_KEY_BTN_MENU; + break; + } + /* Do not repeat already pressed buttons */ + if (!(keycode & KEY_RELEASED) && (s->pressed_keys & event)) { + event = 0; + } + } + + if (event) { + /* Raise GPIO pin first if repeating a key */ + if (!(keycode & KEY_RELEASED) && (s->pressed_keys & event)) { + for (i = 0; i <= 7; i++) { + if (event & (1 << i)) { + qemu_set_irq(s->out[i], 1); + } + } + } + for (i = 0; i <= 7; i++) { + if (event & (1 << i)) { + qemu_set_irq(s->out[i], !!(keycode & KEY_RELEASED)); + } + } + if (keycode & KEY_RELEASED) { + s->pressed_keys &= ~event; + } else { + s->pressed_keys |= event; + } + } + + s->kbd_extended = 0; +} + +static int musicpal_key_init(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + musicpal_key_state *s = MUSICPAL_KEY(dev); + + memory_region_init(&s->iomem, OBJECT(s), "dummy", 0); + sysbus_init_mmio(sbd, &s->iomem); + + s->kbd_extended = 0; + s->pressed_keys = 0; + + qdev_init_gpio_out(dev, s->out, ARRAY_SIZE(s->out)); + + qemu_add_kbd_event_handler(musicpal_key_event, s); + + return 0; +} + +static const VMStateDescription musicpal_key_vmsd = { + .name = "musicpal_key", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(kbd_extended, musicpal_key_state), + VMSTATE_UINT32(pressed_keys, musicpal_key_state), + VMSTATE_END_OF_LIST() + } +}; + +static void musicpal_key_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = musicpal_key_init; + dc->vmsd = &musicpal_key_vmsd; +} + +static const TypeInfo musicpal_key_info = { + .name = TYPE_MUSICPAL_KEY, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(musicpal_key_state), + .class_init = musicpal_key_class_init, +}; + +static struct arm_boot_info musicpal_binfo = { + .loader_start = 0x0, + .board_id = 0x20e, +}; + +static void musicpal_init(MachineState *machine) +{ + const char *cpu_model = machine->cpu_model; + const char *kernel_filename = machine->kernel_filename; + const char *kernel_cmdline = machine->kernel_cmdline; + const char *initrd_filename = machine->initrd_filename; + ARMCPU *cpu; + qemu_irq pic[32]; + DeviceState *dev; + DeviceState *i2c_dev; + DeviceState *lcd_dev; + DeviceState *key_dev; + DeviceState *wm8750_dev; + SysBusDevice *s; + I2CBus *i2c; + int i; + unsigned long flash_size; + DriveInfo *dinfo; + MemoryRegion *address_space_mem = get_system_memory(); + MemoryRegion *ram = g_new(MemoryRegion, 1); + MemoryRegion *sram = g_new(MemoryRegion, 1); + + if (!cpu_model) { + cpu_model = "arm926"; + } + cpu = cpu_arm_init(cpu_model); + if (!cpu) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + + /* For now we use a fixed - the original - RAM size */ + memory_region_allocate_system_memory(ram, NULL, "musicpal.ram", + MP_RAM_DEFAULT_SIZE); + memory_region_add_subregion(address_space_mem, 0, ram); + + memory_region_init_ram(sram, NULL, "musicpal.sram", MP_SRAM_SIZE, + &error_abort); + vmstate_register_ram_global(sram); + memory_region_add_subregion(address_space_mem, MP_SRAM_BASE, sram); + + dev = sysbus_create_simple(TYPE_MV88W8618_PIC, MP_PIC_BASE, + qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ)); + for (i = 0; i < 32; i++) { + pic[i] = qdev_get_gpio_in(dev, i); + } + sysbus_create_varargs(TYPE_MV88W8618_PIT, MP_PIT_BASE, pic[MP_TIMER1_IRQ], + pic[MP_TIMER2_IRQ], pic[MP_TIMER3_IRQ], + pic[MP_TIMER4_IRQ], NULL); + + if (serial_hds[0]) { + serial_mm_init(address_space_mem, MP_UART1_BASE, 2, pic[MP_UART1_IRQ], + 1825000, serial_hds[0], DEVICE_NATIVE_ENDIAN); + } + if (serial_hds[1]) { + serial_mm_init(address_space_mem, MP_UART2_BASE, 2, pic[MP_UART2_IRQ], + 1825000, serial_hds[1], DEVICE_NATIVE_ENDIAN); + } + + /* Register flash */ + dinfo = drive_get(IF_PFLASH, 0, 0); + if (dinfo) { + BlockBackend *blk = blk_by_legacy_dinfo(dinfo); + + flash_size = blk_getlength(blk); + if (flash_size != 8*1024*1024 && flash_size != 16*1024*1024 && + flash_size != 32*1024*1024) { + fprintf(stderr, "Invalid flash image size\n"); + exit(1); + } + + /* + * The original U-Boot accesses the flash at 0xFE000000 instead of + * 0xFF800000 (if there is 8 MB flash). So remap flash access if the + * image is smaller than 32 MB. + */ +#ifdef TARGET_WORDS_BIGENDIAN + pflash_cfi02_register(0x100000000ULL-MP_FLASH_SIZE_MAX, NULL, + "musicpal.flash", flash_size, + blk, 0x10000, (flash_size + 0xffff) >> 16, + MP_FLASH_SIZE_MAX / flash_size, + 2, 0x00BF, 0x236D, 0x0000, 0x0000, + 0x5555, 0x2AAA, 1); +#else + pflash_cfi02_register(0x100000000ULL-MP_FLASH_SIZE_MAX, NULL, + "musicpal.flash", flash_size, + blk, 0x10000, (flash_size + 0xffff) >> 16, + MP_FLASH_SIZE_MAX / flash_size, + 2, 0x00BF, 0x236D, 0x0000, 0x0000, + 0x5555, 0x2AAA, 0); +#endif + + } + sysbus_create_simple(TYPE_MV88W8618_FLASHCFG, MP_FLASHCFG_BASE, NULL); + + qemu_check_nic_model(&nd_table[0], "mv88w8618"); + dev = qdev_create(NULL, TYPE_MV88W8618_ETH); + qdev_set_nic_properties(dev, &nd_table[0]); + qdev_init_nofail(dev); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, MP_ETH_BASE); + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[MP_ETH_IRQ]); + + sysbus_create_simple("mv88w8618_wlan", MP_WLAN_BASE, NULL); + + sysbus_create_simple(TYPE_MUSICPAL_MISC, MP_MISC_BASE, NULL); + + dev = sysbus_create_simple(TYPE_MUSICPAL_GPIO, MP_GPIO_BASE, + pic[MP_GPIO_IRQ]); + i2c_dev = sysbus_create_simple("gpio_i2c", -1, NULL); + i2c = (I2CBus *)qdev_get_child_bus(i2c_dev, "i2c"); + + lcd_dev = sysbus_create_simple(TYPE_MUSICPAL_LCD, MP_LCD_BASE, NULL); + key_dev = sysbus_create_simple(TYPE_MUSICPAL_KEY, -1, NULL); + + /* I2C read data */ + qdev_connect_gpio_out(i2c_dev, 0, + qdev_get_gpio_in(dev, MP_GPIO_I2C_DATA_BIT)); + /* I2C data */ + qdev_connect_gpio_out(dev, 3, qdev_get_gpio_in(i2c_dev, 0)); + /* I2C clock */ + qdev_connect_gpio_out(dev, 4, qdev_get_gpio_in(i2c_dev, 1)); + + for (i = 0; i < 3; i++) { + qdev_connect_gpio_out(dev, i, qdev_get_gpio_in(lcd_dev, i)); + } + for (i = 0; i < 4; i++) { + qdev_connect_gpio_out(key_dev, i, qdev_get_gpio_in(dev, i + 8)); + } + for (i = 4; i < 8; i++) { + qdev_connect_gpio_out(key_dev, i, qdev_get_gpio_in(dev, i + 15)); + } + + wm8750_dev = i2c_create_slave(i2c, "wm8750", MP_WM_ADDR); + dev = qdev_create(NULL, "mv88w8618_audio"); + s = SYS_BUS_DEVICE(dev); + qdev_prop_set_ptr(dev, "wm8750", wm8750_dev); + qdev_init_nofail(dev); + sysbus_mmio_map(s, 0, MP_AUDIO_BASE); + sysbus_connect_irq(s, 0, pic[MP_AUDIO_IRQ]); + + musicpal_binfo.ram_size = MP_RAM_DEFAULT_SIZE; + musicpal_binfo.kernel_filename = kernel_filename; + musicpal_binfo.kernel_cmdline = kernel_cmdline; + musicpal_binfo.initrd_filename = initrd_filename; + arm_load_kernel(cpu, &musicpal_binfo); +} + +static QEMUMachine musicpal_machine = { + .name = "musicpal", + .desc = "Marvell 88w8618 / MusicPal (ARM926EJ-S)", + .init = musicpal_init, +}; + +static void musicpal_machine_init(void) +{ + qemu_register_machine(&musicpal_machine); +} + +machine_init(musicpal_machine_init); + +static void mv88w8618_wlan_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + + sdc->init = mv88w8618_wlan_init; +} + +static const TypeInfo mv88w8618_wlan_info = { + .name = "mv88w8618_wlan", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SysBusDevice), + .class_init = mv88w8618_wlan_class_init, +}; + +static void musicpal_register_types(void) +{ + type_register_static(&mv88w8618_pic_info); + type_register_static(&mv88w8618_pit_info); + type_register_static(&mv88w8618_flashcfg_info); + type_register_static(&mv88w8618_eth_info); + type_register_static(&mv88w8618_wlan_info); + type_register_static(&musicpal_lcd_info); + type_register_static(&musicpal_gpio_info); + type_register_static(&musicpal_key_info); + type_register_static(&musicpal_misc_info); +} + +type_init(musicpal_register_types) diff --git a/qemu/hw/arm/netduino2.c b/qemu/hw/arm/netduino2.c new file mode 100644 index 000000000..8f26780ef --- /dev/null +++ b/qemu/hw/arm/netduino2.c @@ -0,0 +1,57 @@ +/* + * Netduino 2 Machine Model + * + * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "hw/boards.h" +#include "qemu/error-report.h" +#include "hw/arm/stm32f205_soc.h" + +static void netduino2_init(MachineState *machine) +{ + DeviceState *dev; + Error *err = NULL; + + dev = qdev_create(NULL, TYPE_STM32F205_SOC); + if (machine->kernel_filename) { + qdev_prop_set_string(dev, "kernel-filename", machine->kernel_filename); + } + qdev_prop_set_string(dev, "cpu-model", "cortex-m3"); + object_property_set_bool(OBJECT(dev), true, "realized", &err); + if (err != NULL) { + error_report("%s", error_get_pretty(err)); + exit(1); + } +} + +static QEMUMachine netduino2_machine = { + .name = "netduino2", + .desc = "Netduino 2 Machine", + .init = netduino2_init, +}; + +static void netduino2_machine_init(void) +{ + qemu_register_machine(&netduino2_machine); +} + +machine_init(netduino2_machine_init); diff --git a/qemu/hw/arm/nseries.c b/qemu/hw/arm/nseries.c new file mode 100644 index 000000000..a659e8525 --- /dev/null +++ b/qemu/hw/arm/nseries.c @@ -0,0 +1,1436 @@ +/* + * Nokia N-series internet tablets. + * + * Copyright (C) 2007 Nokia Corporation + * Written by Andrzej Zaborowski <andrew@openedhand.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu-common.h" +#include "sysemu/sysemu.h" +#include "hw/arm/omap.h" +#include "hw/arm/arm.h" +#include "hw/irq.h" +#include "ui/console.h" +#include "hw/boards.h" +#include "hw/i2c/i2c.h" +#include "hw/devices.h" +#include "hw/block/flash.h" +#include "hw/hw.h" +#include "hw/bt.h" +#include "hw/loader.h" +#include "sysemu/block-backend.h" +#include "hw/sysbus.h" +#include "exec/address-spaces.h" + +/* Nokia N8x0 support */ +struct n800_s { + struct omap_mpu_state_s *mpu; + + struct rfbi_chip_s blizzard; + struct { + void *opaque; + uint32_t (*txrx)(void *opaque, uint32_t value, int len); + uWireSlave *chip; + } ts; + + int keymap[0x80]; + DeviceState *kbd; + + DeviceState *usb; + void *retu; + void *tahvo; + DeviceState *nand; +}; + +/* GPIO pins */ +#define N8X0_TUSB_ENABLE_GPIO 0 +#define N800_MMC2_WP_GPIO 8 +#define N800_UNKNOWN_GPIO0 9 /* out */ +#define N810_MMC2_VIOSD_GPIO 9 +#define N810_HEADSET_AMP_GPIO 10 +#define N800_CAM_TURN_GPIO 12 +#define N810_GPS_RESET_GPIO 12 +#define N800_BLIZZARD_POWERDOWN_GPIO 15 +#define N800_MMC1_WP_GPIO 23 +#define N810_MMC2_VSD_GPIO 23 +#define N8X0_ONENAND_GPIO 26 +#define N810_BLIZZARD_RESET_GPIO 30 +#define N800_UNKNOWN_GPIO2 53 /* out */ +#define N8X0_TUSB_INT_GPIO 58 +#define N8X0_BT_WKUP_GPIO 61 +#define N8X0_STI_GPIO 62 +#define N8X0_CBUS_SEL_GPIO 64 +#define N8X0_CBUS_DAT_GPIO 65 +#define N8X0_CBUS_CLK_GPIO 66 +#define N8X0_WLAN_IRQ_GPIO 87 +#define N8X0_BT_RESET_GPIO 92 +#define N8X0_TEA5761_CS_GPIO 93 +#define N800_UNKNOWN_GPIO 94 +#define N810_TSC_RESET_GPIO 94 +#define N800_CAM_ACT_GPIO 95 +#define N810_GPS_WAKEUP_GPIO 95 +#define N8X0_MMC_CS_GPIO 96 +#define N8X0_WLAN_PWR_GPIO 97 +#define N8X0_BT_HOST_WKUP_GPIO 98 +#define N810_SPEAKER_AMP_GPIO 101 +#define N810_KB_LOCK_GPIO 102 +#define N800_TSC_TS_GPIO 103 +#define N810_TSC_TS_GPIO 106 +#define N8X0_HEADPHONE_GPIO 107 +#define N8X0_RETU_GPIO 108 +#define N800_TSC_KP_IRQ_GPIO 109 +#define N810_KEYBOARD_GPIO 109 +#define N800_BAT_COVER_GPIO 110 +#define N810_SLIDE_GPIO 110 +#define N8X0_TAHVO_GPIO 111 +#define N800_UNKNOWN_GPIO4 112 /* out */ +#define N810_SLEEPX_LED_GPIO 112 +#define N800_TSC_RESET_GPIO 118 /* ? */ +#define N810_AIC33_RESET_GPIO 118 +#define N800_TSC_UNKNOWN_GPIO 119 /* out */ +#define N8X0_TMP105_GPIO 125 + +/* Config */ +#define BT_UART 0 +#define XLDR_LL_UART 1 + +/* Addresses on the I2C bus 0 */ +#define N810_TLV320AIC33_ADDR 0x18 /* Audio CODEC */ +#define N8X0_TCM825x_ADDR 0x29 /* Camera */ +#define N810_LP5521_ADDR 0x32 /* LEDs */ +#define N810_TSL2563_ADDR 0x3d /* Light sensor */ +#define N810_LM8323_ADDR 0x45 /* Keyboard */ +/* Addresses on the I2C bus 1 */ +#define N8X0_TMP105_ADDR 0x48 /* Temperature sensor */ +#define N8X0_MENELAUS_ADDR 0x72 /* Power management */ + +/* Chipselects on GPMC NOR interface */ +#define N8X0_ONENAND_CS 0 +#define N8X0_USB_ASYNC_CS 1 +#define N8X0_USB_SYNC_CS 4 + +#define N8X0_BD_ADDR 0x00, 0x1a, 0x89, 0x9e, 0x3e, 0x81 + +static void n800_mmc_cs_cb(void *opaque, int line, int level) +{ + /* TODO: this seems to actually be connected to the menelaus, to + * which also both MMC slots connect. */ + omap_mmc_enable((struct omap_mmc_s *) opaque, !level); +} + +static void n8x0_gpio_setup(struct n800_s *s) +{ + qdev_connect_gpio_out(s->mpu->gpio, N8X0_MMC_CS_GPIO, + qemu_allocate_irq(n800_mmc_cs_cb, s->mpu->mmc, 0)); + qemu_irq_lower(qdev_get_gpio_in(s->mpu->gpio, N800_BAT_COVER_GPIO)); +} + +#define MAEMO_CAL_HEADER(...) \ + 'C', 'o', 'n', 'F', 0x02, 0x00, 0x04, 0x00, \ + __VA_ARGS__, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + +static const uint8_t n8x0_cal_wlan_mac[] = { + MAEMO_CAL_HEADER('w', 'l', 'a', 'n', '-', 'm', 'a', 'c') + 0x1c, 0x00, 0x00, 0x00, 0x47, 0xd6, 0x69, 0xb3, + 0x30, 0x08, 0xa0, 0x83, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, + 0x89, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00, 0x00, + 0x5d, 0x00, 0x00, 0x00, 0xc1, 0x00, 0x00, 0x00, +}; + +static const uint8_t n8x0_cal_bt_id[] = { + MAEMO_CAL_HEADER('b', 't', '-', 'i', 'd', 0, 0, 0) + 0x0a, 0x00, 0x00, 0x00, 0xa3, 0x4b, 0xf6, 0x96, + 0xa8, 0xeb, 0xb2, 0x41, 0x00, 0x00, 0x00, 0x00, + N8X0_BD_ADDR, +}; + +static void n8x0_nand_setup(struct n800_s *s) +{ + char *otp_region; + DriveInfo *dinfo; + + s->nand = qdev_create(NULL, "onenand"); + qdev_prop_set_uint16(s->nand, "manufacturer_id", NAND_MFR_SAMSUNG); + /* Either 0x40 or 0x48 are OK for the device ID */ + qdev_prop_set_uint16(s->nand, "device_id", 0x48); + qdev_prop_set_uint16(s->nand, "version_id", 0); + qdev_prop_set_int32(s->nand, "shift", 1); + dinfo = drive_get(IF_MTD, 0, 0); + if (dinfo) { + qdev_prop_set_drive_nofail(s->nand, "drive", + blk_by_legacy_dinfo(dinfo)); + } + qdev_init_nofail(s->nand); + sysbus_connect_irq(SYS_BUS_DEVICE(s->nand), 0, + qdev_get_gpio_in(s->mpu->gpio, N8X0_ONENAND_GPIO)); + omap_gpmc_attach(s->mpu->gpmc, N8X0_ONENAND_CS, + sysbus_mmio_get_region(SYS_BUS_DEVICE(s->nand), 0)); + otp_region = onenand_raw_otp(s->nand); + + memcpy(otp_region + 0x000, n8x0_cal_wlan_mac, sizeof(n8x0_cal_wlan_mac)); + memcpy(otp_region + 0x800, n8x0_cal_bt_id, sizeof(n8x0_cal_bt_id)); + /* XXX: in theory should also update the OOB for both pages */ +} + +static qemu_irq n8x0_system_powerdown; + +static void n8x0_powerdown_req(Notifier *n, void *opaque) +{ + qemu_irq_raise(n8x0_system_powerdown); +} + +static Notifier n8x0_system_powerdown_notifier = { + .notify = n8x0_powerdown_req +}; + +static void n8x0_i2c_setup(struct n800_s *s) +{ + DeviceState *dev; + qemu_irq tmp_irq = qdev_get_gpio_in(s->mpu->gpio, N8X0_TMP105_GPIO); + I2CBus *i2c = omap_i2c_bus(s->mpu->i2c[0]); + + /* Attach a menelaus PM chip */ + dev = i2c_create_slave(i2c, "twl92230", N8X0_MENELAUS_ADDR); + qdev_connect_gpio_out(dev, 3, + qdev_get_gpio_in(s->mpu->ih[0], + OMAP_INT_24XX_SYS_NIRQ)); + + n8x0_system_powerdown = qdev_get_gpio_in(dev, 3); + qemu_register_powerdown_notifier(&n8x0_system_powerdown_notifier); + + /* Attach a TMP105 PM chip (A0 wired to ground) */ + dev = i2c_create_slave(i2c, "tmp105", N8X0_TMP105_ADDR); + qdev_connect_gpio_out(dev, 0, tmp_irq); +} + +/* Touchscreen and keypad controller */ +static MouseTransformInfo n800_pointercal = { + .x = 800, + .y = 480, + .a = { 14560, -68, -3455208, -39, -9621, 35152972, 65536 }, +}; + +static MouseTransformInfo n810_pointercal = { + .x = 800, + .y = 480, + .a = { 15041, 148, -4731056, 171, -10238, 35933380, 65536 }, +}; + +#define RETU_KEYCODE 61 /* F3 */ + +static void n800_key_event(void *opaque, int keycode) +{ + struct n800_s *s = (struct n800_s *) opaque; + int code = s->keymap[keycode & 0x7f]; + + if (code == -1) { + if ((keycode & 0x7f) == RETU_KEYCODE) { + retu_key_event(s->retu, !(keycode & 0x80)); + } + return; + } + + tsc210x_key_event(s->ts.chip, code, !(keycode & 0x80)); +} + +static const int n800_keys[16] = { + -1, + 72, /* Up */ + 63, /* Home (F5) */ + -1, + 75, /* Left */ + 28, /* Enter */ + 77, /* Right */ + -1, + 1, /* Cycle (ESC) */ + 80, /* Down */ + 62, /* Menu (F4) */ + -1, + 66, /* Zoom- (F8) */ + 64, /* FullScreen (F6) */ + 65, /* Zoom+ (F7) */ + -1, +}; + +static void n800_tsc_kbd_setup(struct n800_s *s) +{ + int i; + + /* XXX: are the three pins inverted inside the chip between the + * tsc and the cpu (N4111)? */ + qemu_irq penirq = NULL; /* NC */ + qemu_irq kbirq = qdev_get_gpio_in(s->mpu->gpio, N800_TSC_KP_IRQ_GPIO); + qemu_irq dav = qdev_get_gpio_in(s->mpu->gpio, N800_TSC_TS_GPIO); + + s->ts.chip = tsc2301_init(penirq, kbirq, dav); + s->ts.opaque = s->ts.chip->opaque; + s->ts.txrx = tsc210x_txrx; + + for (i = 0; i < 0x80; i++) { + s->keymap[i] = -1; + } + for (i = 0; i < 0x10; i++) { + if (n800_keys[i] >= 0) { + s->keymap[n800_keys[i]] = i; + } + } + + qemu_add_kbd_event_handler(n800_key_event, s); + + tsc210x_set_transform(s->ts.chip, &n800_pointercal); +} + +static void n810_tsc_setup(struct n800_s *s) +{ + qemu_irq pintdav = qdev_get_gpio_in(s->mpu->gpio, N810_TSC_TS_GPIO); + + s->ts.opaque = tsc2005_init(pintdav); + s->ts.txrx = tsc2005_txrx; + + tsc2005_set_transform(s->ts.opaque, &n810_pointercal); +} + +/* N810 Keyboard controller */ +static void n810_key_event(void *opaque, int keycode) +{ + struct n800_s *s = (struct n800_s *) opaque; + int code = s->keymap[keycode & 0x7f]; + + if (code == -1) { + if ((keycode & 0x7f) == RETU_KEYCODE) { + retu_key_event(s->retu, !(keycode & 0x80)); + } + return; + } + + lm832x_key_event(s->kbd, code, !(keycode & 0x80)); +} + +#define M 0 + +static int n810_keys[0x80] = { + [0x01] = 16, /* Q */ + [0x02] = 37, /* K */ + [0x03] = 24, /* O */ + [0x04] = 25, /* P */ + [0x05] = 14, /* Backspace */ + [0x06] = 30, /* A */ + [0x07] = 31, /* S */ + [0x08] = 32, /* D */ + [0x09] = 33, /* F */ + [0x0a] = 34, /* G */ + [0x0b] = 35, /* H */ + [0x0c] = 36, /* J */ + + [0x11] = 17, /* W */ + [0x12] = 62, /* Menu (F4) */ + [0x13] = 38, /* L */ + [0x14] = 40, /* ' (Apostrophe) */ + [0x16] = 44, /* Z */ + [0x17] = 45, /* X */ + [0x18] = 46, /* C */ + [0x19] = 47, /* V */ + [0x1a] = 48, /* B */ + [0x1b] = 49, /* N */ + [0x1c] = 42, /* Shift (Left shift) */ + [0x1f] = 65, /* Zoom+ (F7) */ + + [0x21] = 18, /* E */ + [0x22] = 39, /* ; (Semicolon) */ + [0x23] = 12, /* - (Minus) */ + [0x24] = 13, /* = (Equal) */ + [0x2b] = 56, /* Fn (Left Alt) */ + [0x2c] = 50, /* M */ + [0x2f] = 66, /* Zoom- (F8) */ + + [0x31] = 19, /* R */ + [0x32] = 29 | M, /* Right Ctrl */ + [0x34] = 57, /* Space */ + [0x35] = 51, /* , (Comma) */ + [0x37] = 72 | M, /* Up */ + [0x3c] = 82 | M, /* Compose (Insert) */ + [0x3f] = 64, /* FullScreen (F6) */ + + [0x41] = 20, /* T */ + [0x44] = 52, /* . (Dot) */ + [0x46] = 77 | M, /* Right */ + [0x4f] = 63, /* Home (F5) */ + [0x51] = 21, /* Y */ + [0x53] = 80 | M, /* Down */ + [0x55] = 28, /* Enter */ + [0x5f] = 1, /* Cycle (ESC) */ + + [0x61] = 22, /* U */ + [0x64] = 75 | M, /* Left */ + + [0x71] = 23, /* I */ +#if 0 + [0x75] = 28 | M, /* KP Enter (KP Enter) */ +#else + [0x75] = 15, /* KP Enter (Tab) */ +#endif +}; + +#undef M + +static void n810_kbd_setup(struct n800_s *s) +{ + qemu_irq kbd_irq = qdev_get_gpio_in(s->mpu->gpio, N810_KEYBOARD_GPIO); + int i; + + for (i = 0; i < 0x80; i++) { + s->keymap[i] = -1; + } + for (i = 0; i < 0x80; i++) { + if (n810_keys[i] > 0) { + s->keymap[n810_keys[i]] = i; + } + } + + qemu_add_kbd_event_handler(n810_key_event, s); + + /* Attach the LM8322 keyboard to the I2C bus, + * should happen in n8x0_i2c_setup and s->kbd be initialised here. */ + s->kbd = i2c_create_slave(omap_i2c_bus(s->mpu->i2c[0]), + "lm8323", N810_LM8323_ADDR); + qdev_connect_gpio_out(s->kbd, 0, kbd_irq); +} + +/* LCD MIPI DBI-C controller (URAL) */ +struct mipid_s { + int resp[4]; + int param[4]; + int p; + int pm; + int cmd; + + int sleep; + int booster; + int te; + int selfcheck; + int partial; + int normal; + int vscr; + int invert; + int onoff; + int gamma; + uint32_t id; +}; + +static void mipid_reset(struct mipid_s *s) +{ + s->pm = 0; + s->cmd = 0; + + s->sleep = 1; + s->booster = 0; + s->selfcheck = + (1 << 7) | /* Register loading OK. */ + (1 << 5) | /* The chip is attached. */ + (1 << 4); /* Display glass still in one piece. */ + s->te = 0; + s->partial = 0; + s->normal = 1; + s->vscr = 0; + s->invert = 0; + s->onoff = 1; + s->gamma = 0; +} + +static uint32_t mipid_txrx(void *opaque, uint32_t cmd, int len) +{ + struct mipid_s *s = (struct mipid_s *) opaque; + uint8_t ret; + + if (len > 9) { + hw_error("%s: FIXME: bad SPI word width %i\n", __FUNCTION__, len); + } + + if (s->p >= ARRAY_SIZE(s->resp)) { + ret = 0; + } else { + ret = s->resp[s->p++]; + } + if (s->pm-- > 0) { + s->param[s->pm] = cmd; + } else { + s->cmd = cmd; + } + + switch (s->cmd) { + case 0x00: /* NOP */ + break; + + case 0x01: /* SWRESET */ + mipid_reset(s); + break; + + case 0x02: /* BSTROFF */ + s->booster = 0; + break; + case 0x03: /* BSTRON */ + s->booster = 1; + break; + + case 0x04: /* RDDID */ + s->p = 0; + s->resp[0] = (s->id >> 16) & 0xff; + s->resp[1] = (s->id >> 8) & 0xff; + s->resp[2] = (s->id >> 0) & 0xff; + break; + + case 0x06: /* RD_RED */ + case 0x07: /* RD_GREEN */ + /* XXX the bootloader sometimes issues RD_BLUE meaning RDDID so + * for the bootloader one needs to change this. */ + case 0x08: /* RD_BLUE */ + s->p = 0; + /* TODO: return first pixel components */ + s->resp[0] = 0x01; + break; + + case 0x09: /* RDDST */ + s->p = 0; + s->resp[0] = s->booster << 7; + s->resp[1] = (5 << 4) | (s->partial << 2) | + (s->sleep << 1) | s->normal; + s->resp[2] = (s->vscr << 7) | (s->invert << 5) | + (s->onoff << 2) | (s->te << 1) | (s->gamma >> 2); + s->resp[3] = s->gamma << 6; + break; + + case 0x0a: /* RDDPM */ + s->p = 0; + s->resp[0] = (s->onoff << 2) | (s->normal << 3) | (s->sleep << 4) | + (s->partial << 5) | (s->sleep << 6) | (s->booster << 7); + break; + case 0x0b: /* RDDMADCTR */ + s->p = 0; + s->resp[0] = 0; + break; + case 0x0c: /* RDDCOLMOD */ + s->p = 0; + s->resp[0] = 5; /* 65K colours */ + break; + case 0x0d: /* RDDIM */ + s->p = 0; + s->resp[0] = (s->invert << 5) | (s->vscr << 7) | s->gamma; + break; + case 0x0e: /* RDDSM */ + s->p = 0; + s->resp[0] = s->te << 7; + break; + case 0x0f: /* RDDSDR */ + s->p = 0; + s->resp[0] = s->selfcheck; + break; + + case 0x10: /* SLPIN */ + s->sleep = 1; + break; + case 0x11: /* SLPOUT */ + s->sleep = 0; + s->selfcheck ^= 1 << 6; /* POFF self-diagnosis Ok */ + break; + + case 0x12: /* PTLON */ + s->partial = 1; + s->normal = 0; + s->vscr = 0; + break; + case 0x13: /* NORON */ + s->partial = 0; + s->normal = 1; + s->vscr = 0; + break; + + case 0x20: /* INVOFF */ + s->invert = 0; + break; + case 0x21: /* INVON */ + s->invert = 1; + break; + + case 0x22: /* APOFF */ + case 0x23: /* APON */ + goto bad_cmd; + + case 0x25: /* WRCNTR */ + if (s->pm < 0) { + s->pm = 1; + } + goto bad_cmd; + + case 0x26: /* GAMSET */ + if (!s->pm) { + s->gamma = ctz32(s->param[0] & 0xf); + if (s->gamma == 32) { + s->gamma = -1; /* XXX: should this be 0? */ + } + } else if (s->pm < 0) { + s->pm = 1; + } + break; + + case 0x28: /* DISPOFF */ + s->onoff = 0; + break; + case 0x29: /* DISPON */ + s->onoff = 1; + break; + + case 0x2a: /* CASET */ + case 0x2b: /* RASET */ + case 0x2c: /* RAMWR */ + case 0x2d: /* RGBSET */ + case 0x2e: /* RAMRD */ + case 0x30: /* PTLAR */ + case 0x33: /* SCRLAR */ + goto bad_cmd; + + case 0x34: /* TEOFF */ + s->te = 0; + break; + case 0x35: /* TEON */ + if (!s->pm) { + s->te = 1; + } else if (s->pm < 0) { + s->pm = 1; + } + break; + + case 0x36: /* MADCTR */ + goto bad_cmd; + + case 0x37: /* VSCSAD */ + s->partial = 0; + s->normal = 0; + s->vscr = 1; + break; + + case 0x38: /* IDMOFF */ + case 0x39: /* IDMON */ + case 0x3a: /* COLMOD */ + goto bad_cmd; + + case 0xb0: /* CLKINT / DISCTL */ + case 0xb1: /* CLKEXT */ + if (s->pm < 0) { + s->pm = 2; + } + break; + + case 0xb4: /* FRMSEL */ + break; + + case 0xb5: /* FRM8SEL */ + case 0xb6: /* TMPRNG / INIESC */ + case 0xb7: /* TMPHIS / NOP2 */ + case 0xb8: /* TMPREAD / MADCTL */ + case 0xba: /* DISTCTR */ + case 0xbb: /* EPVOL */ + goto bad_cmd; + + case 0xbd: /* Unknown */ + s->p = 0; + s->resp[0] = 0; + s->resp[1] = 1; + break; + + case 0xc2: /* IFMOD */ + if (s->pm < 0) { + s->pm = 2; + } + break; + + case 0xc6: /* PWRCTL */ + case 0xc7: /* PPWRCTL */ + case 0xd0: /* EPWROUT */ + case 0xd1: /* EPWRIN */ + case 0xd4: /* RDEV */ + case 0xd5: /* RDRR */ + goto bad_cmd; + + case 0xda: /* RDID1 */ + s->p = 0; + s->resp[0] = (s->id >> 16) & 0xff; + break; + case 0xdb: /* RDID2 */ + s->p = 0; + s->resp[0] = (s->id >> 8) & 0xff; + break; + case 0xdc: /* RDID3 */ + s->p = 0; + s->resp[0] = (s->id >> 0) & 0xff; + break; + + default: + bad_cmd: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: unknown command %02x\n", __func__, s->cmd); + break; + } + + return ret; +} + +static void *mipid_init(void) +{ + struct mipid_s *s = (struct mipid_s *) g_malloc0(sizeof(*s)); + + s->id = 0x838f03; + mipid_reset(s); + + return s; +} + +static void n8x0_spi_setup(struct n800_s *s) +{ + void *tsc = s->ts.opaque; + void *mipid = mipid_init(); + + omap_mcspi_attach(s->mpu->mcspi[0], s->ts.txrx, tsc, 0); + omap_mcspi_attach(s->mpu->mcspi[0], mipid_txrx, mipid, 1); +} + +/* This task is normally performed by the bootloader. If we're loading + * a kernel directly, we need to enable the Blizzard ourselves. */ +static void n800_dss_init(struct rfbi_chip_s *chip) +{ + uint8_t *fb_blank; + + chip->write(chip->opaque, 0, 0x2a); /* LCD Width register */ + chip->write(chip->opaque, 1, 0x64); + chip->write(chip->opaque, 0, 0x2c); /* LCD HNDP register */ + chip->write(chip->opaque, 1, 0x1e); + chip->write(chip->opaque, 0, 0x2e); /* LCD Height 0 register */ + chip->write(chip->opaque, 1, 0xe0); + chip->write(chip->opaque, 0, 0x30); /* LCD Height 1 register */ + chip->write(chip->opaque, 1, 0x01); + chip->write(chip->opaque, 0, 0x32); /* LCD VNDP register */ + chip->write(chip->opaque, 1, 0x06); + chip->write(chip->opaque, 0, 0x68); /* Display Mode register */ + chip->write(chip->opaque, 1, 1); /* Enable bit */ + + chip->write(chip->opaque, 0, 0x6c); + chip->write(chip->opaque, 1, 0x00); /* Input X Start Position */ + chip->write(chip->opaque, 1, 0x00); /* Input X Start Position */ + chip->write(chip->opaque, 1, 0x00); /* Input Y Start Position */ + chip->write(chip->opaque, 1, 0x00); /* Input Y Start Position */ + chip->write(chip->opaque, 1, 0x1f); /* Input X End Position */ + chip->write(chip->opaque, 1, 0x03); /* Input X End Position */ + chip->write(chip->opaque, 1, 0xdf); /* Input Y End Position */ + chip->write(chip->opaque, 1, 0x01); /* Input Y End Position */ + chip->write(chip->opaque, 1, 0x00); /* Output X Start Position */ + chip->write(chip->opaque, 1, 0x00); /* Output X Start Position */ + chip->write(chip->opaque, 1, 0x00); /* Output Y Start Position */ + chip->write(chip->opaque, 1, 0x00); /* Output Y Start Position */ + chip->write(chip->opaque, 1, 0x1f); /* Output X End Position */ + chip->write(chip->opaque, 1, 0x03); /* Output X End Position */ + chip->write(chip->opaque, 1, 0xdf); /* Output Y End Position */ + chip->write(chip->opaque, 1, 0x01); /* Output Y End Position */ + chip->write(chip->opaque, 1, 0x01); /* Input Data Format */ + chip->write(chip->opaque, 1, 0x01); /* Data Source Select */ + + fb_blank = memset(g_malloc(800 * 480 * 2), 0xff, 800 * 480 * 2); + /* Display Memory Data Port */ + chip->block(chip->opaque, 1, fb_blank, 800 * 480 * 2, 800); + g_free(fb_blank); +} + +static void n8x0_dss_setup(struct n800_s *s) +{ + s->blizzard.opaque = s1d13745_init(NULL); + s->blizzard.block = s1d13745_write_block; + s->blizzard.write = s1d13745_write; + s->blizzard.read = s1d13745_read; + + omap_rfbi_attach(s->mpu->dss, 0, &s->blizzard); +} + +static void n8x0_cbus_setup(struct n800_s *s) +{ + qemu_irq dat_out = qdev_get_gpio_in(s->mpu->gpio, N8X0_CBUS_DAT_GPIO); + qemu_irq retu_irq = qdev_get_gpio_in(s->mpu->gpio, N8X0_RETU_GPIO); + qemu_irq tahvo_irq = qdev_get_gpio_in(s->mpu->gpio, N8X0_TAHVO_GPIO); + + CBus *cbus = cbus_init(dat_out); + + qdev_connect_gpio_out(s->mpu->gpio, N8X0_CBUS_CLK_GPIO, cbus->clk); + qdev_connect_gpio_out(s->mpu->gpio, N8X0_CBUS_DAT_GPIO, cbus->dat); + qdev_connect_gpio_out(s->mpu->gpio, N8X0_CBUS_SEL_GPIO, cbus->sel); + + cbus_attach(cbus, s->retu = retu_init(retu_irq, 1)); + cbus_attach(cbus, s->tahvo = tahvo_init(tahvo_irq, 1)); +} + +static void n8x0_uart_setup(struct n800_s *s) +{ + CharDriverState *radio = uart_hci_init( + qdev_get_gpio_in(s->mpu->gpio, N8X0_BT_HOST_WKUP_GPIO)); + + qdev_connect_gpio_out(s->mpu->gpio, N8X0_BT_RESET_GPIO, + csrhci_pins_get(radio)[csrhci_pin_reset]); + qdev_connect_gpio_out(s->mpu->gpio, N8X0_BT_WKUP_GPIO, + csrhci_pins_get(radio)[csrhci_pin_wakeup]); + + omap_uart_attach(s->mpu->uart[BT_UART], radio); +} + +static void n8x0_usb_setup(struct n800_s *s) +{ + SysBusDevice *dev; + s->usb = qdev_create(NULL, "tusb6010"); + dev = SYS_BUS_DEVICE(s->usb); + qdev_init_nofail(s->usb); + sysbus_connect_irq(dev, 0, + qdev_get_gpio_in(s->mpu->gpio, N8X0_TUSB_INT_GPIO)); + /* Using the NOR interface */ + omap_gpmc_attach(s->mpu->gpmc, N8X0_USB_ASYNC_CS, + sysbus_mmio_get_region(dev, 0)); + omap_gpmc_attach(s->mpu->gpmc, N8X0_USB_SYNC_CS, + sysbus_mmio_get_region(dev, 1)); + qdev_connect_gpio_out(s->mpu->gpio, N8X0_TUSB_ENABLE_GPIO, + qdev_get_gpio_in(s->usb, 0)); /* tusb_pwr */ +} + +/* Setup done before the main bootloader starts by some early setup code + * - used when we want to run the main bootloader in emulation. This + * isn't documented. */ +static uint32_t n800_pinout[104] = { + 0x080f00d8, 0x00d40808, 0x03080808, 0x080800d0, + 0x00dc0808, 0x0b0f0f00, 0x080800b4, 0x00c00808, + 0x08080808, 0x180800c4, 0x00b80000, 0x08080808, + 0x080800bc, 0x00cc0808, 0x08081818, 0x18180128, + 0x01241800, 0x18181818, 0x000000f0, 0x01300000, + 0x00001b0b, 0x1b0f0138, 0x00e0181b, 0x1b031b0b, + 0x180f0078, 0x00740018, 0x0f0f0f1a, 0x00000080, + 0x007c0000, 0x00000000, 0x00000088, 0x00840000, + 0x00000000, 0x00000094, 0x00980300, 0x0f180003, + 0x0000008c, 0x00900f0f, 0x0f0f1b00, 0x0f00009c, + 0x01140000, 0x1b1b0f18, 0x0818013c, 0x01400008, + 0x00001818, 0x000b0110, 0x010c1800, 0x0b030b0f, + 0x181800f4, 0x00f81818, 0x00000018, 0x000000fc, + 0x00401808, 0x00000000, 0x0f1b0030, 0x003c0008, + 0x00000000, 0x00000038, 0x00340000, 0x00000000, + 0x1a080070, 0x00641a1a, 0x08080808, 0x08080060, + 0x005c0808, 0x08080808, 0x08080058, 0x00540808, + 0x08080808, 0x0808006c, 0x00680808, 0x08080808, + 0x000000a8, 0x00b00000, 0x08080808, 0x000000a0, + 0x00a40000, 0x00000000, 0x08ff0050, 0x004c0808, + 0xffffffff, 0xffff0048, 0x0044ffff, 0xffffffff, + 0x000000ac, 0x01040800, 0x08080b0f, 0x18180100, + 0x01081818, 0x0b0b1808, 0x1a0300e4, 0x012c0b1a, + 0x02020018, 0x0b000134, 0x011c0800, 0x0b1b1b00, + 0x0f0000c8, 0x00ec181b, 0x000f0f02, 0x00180118, + 0x01200000, 0x0f0b1b1b, 0x0f0200e8, 0x0000020b, +}; + +static void n800_setup_nolo_tags(void *sram_base) +{ + int i; + uint32_t *p = sram_base + 0x8000; + uint32_t *v = sram_base + 0xa000; + + memset(p, 0, 0x3000); + + strcpy((void *) (p + 0), "QEMU N800"); + + strcpy((void *) (p + 8), "F5"); + + stl_p(p + 10, 0x04f70000); + strcpy((void *) (p + 9), "RX-34"); + + /* RAM size in MB? */ + stl_p(p + 12, 0x80); + + /* Pointer to the list of tags */ + stl_p(p + 13, OMAP2_SRAM_BASE + 0x9000); + + /* The NOLO tags start here */ + p = sram_base + 0x9000; +#define ADD_TAG(tag, len) \ + stw_p((uint16_t *) p + 0, tag); \ + stw_p((uint16_t *) p + 1, len); p++; \ + stl_p(p++, OMAP2_SRAM_BASE | (((void *) v - sram_base) & 0xffff)); + + /* OMAP STI console? Pin out settings? */ + ADD_TAG(0x6e01, 414); + for (i = 0; i < ARRAY_SIZE(n800_pinout); i++) { + stl_p(v++, n800_pinout[i]); + } + + /* Kernel memsize? */ + ADD_TAG(0x6e05, 1); + stl_p(v++, 2); + + /* NOLO serial console */ + ADD_TAG(0x6e02, 4); + stl_p(v++, XLDR_LL_UART); /* UART number (1 - 3) */ + +#if 0 + /* CBUS settings (Retu/AVilma) */ + ADD_TAG(0x6e03, 6); + stw_p((uint16_t *) v + 0, 65); /* CBUS GPIO0 */ + stw_p((uint16_t *) v + 1, 66); /* CBUS GPIO1 */ + stw_p((uint16_t *) v + 2, 64); /* CBUS GPIO2 */ + v += 2; +#endif + + /* Nokia ASIC BB5 (Retu/Tahvo) */ + ADD_TAG(0x6e0a, 4); + stw_p((uint16_t *) v + 0, 111); /* "Retu" interrupt GPIO */ + stw_p((uint16_t *) v + 1, 108); /* "Tahvo" interrupt GPIO */ + v++; + + /* LCD console? */ + ADD_TAG(0x6e04, 4); + stw_p((uint16_t *) v + 0, 30); /* ??? */ + stw_p((uint16_t *) v + 1, 24); /* ??? */ + v++; + +#if 0 + /* LCD settings */ + ADD_TAG(0x6e06, 2); + stw_p((uint16_t *) (v++), 15); /* ??? */ +#endif + + /* I^2C (Menelaus) */ + ADD_TAG(0x6e07, 4); + stl_p(v++, 0x00720000); /* ??? */ + + /* Unknown */ + ADD_TAG(0x6e0b, 6); + stw_p((uint16_t *) v + 0, 94); /* ??? */ + stw_p((uint16_t *) v + 1, 23); /* ??? */ + stw_p((uint16_t *) v + 2, 0); /* ??? */ + v += 2; + + /* OMAP gpio switch info */ + ADD_TAG(0x6e0c, 80); + strcpy((void *) v, "bat_cover"); v += 3; + stw_p((uint16_t *) v + 0, 110); /* GPIO num ??? */ + stw_p((uint16_t *) v + 1, 1); /* GPIO num ??? */ + v += 2; + strcpy((void *) v, "cam_act"); v += 3; + stw_p((uint16_t *) v + 0, 95); /* GPIO num ??? */ + stw_p((uint16_t *) v + 1, 32); /* GPIO num ??? */ + v += 2; + strcpy((void *) v, "cam_turn"); v += 3; + stw_p((uint16_t *) v + 0, 12); /* GPIO num ??? */ + stw_p((uint16_t *) v + 1, 33); /* GPIO num ??? */ + v += 2; + strcpy((void *) v, "headphone"); v += 3; + stw_p((uint16_t *) v + 0, 107); /* GPIO num ??? */ + stw_p((uint16_t *) v + 1, 17); /* GPIO num ??? */ + v += 2; + + /* Bluetooth */ + ADD_TAG(0x6e0e, 12); + stl_p(v++, 0x5c623d01); /* ??? */ + stl_p(v++, 0x00000201); /* ??? */ + stl_p(v++, 0x00000000); /* ??? */ + + /* CX3110x WLAN settings */ + ADD_TAG(0x6e0f, 8); + stl_p(v++, 0x00610025); /* ??? */ + stl_p(v++, 0xffff0057); /* ??? */ + + /* MMC host settings */ + ADD_TAG(0x6e10, 12); + stl_p(v++, 0xffff000f); /* ??? */ + stl_p(v++, 0xffffffff); /* ??? */ + stl_p(v++, 0x00000060); /* ??? */ + + /* OneNAND chip select */ + ADD_TAG(0x6e11, 10); + stl_p(v++, 0x00000401); /* ??? */ + stl_p(v++, 0x0002003a); /* ??? */ + stl_p(v++, 0x00000002); /* ??? */ + + /* TEA5761 sensor settings */ + ADD_TAG(0x6e12, 2); + stl_p(v++, 93); /* GPIO num ??? */ + +#if 0 + /* Unknown tag */ + ADD_TAG(6e09, 0); + + /* Kernel UART / console */ + ADD_TAG(6e12, 0); +#endif + + /* End of the list */ + stl_p(p++, 0x00000000); + stl_p(p++, 0x00000000); +} + +/* This task is normally performed by the bootloader. If we're loading + * a kernel directly, we need to set up GPMC mappings ourselves. */ +static void n800_gpmc_init(struct n800_s *s) +{ + uint32_t config7 = + (0xf << 8) | /* MASKADDRESS */ + (1 << 6) | /* CSVALID */ + (4 << 0); /* BASEADDRESS */ + + cpu_physical_memory_write(0x6800a078, /* GPMC_CONFIG7_0 */ + &config7, sizeof(config7)); +} + +/* Setup sequence done by the bootloader */ +static void n8x0_boot_init(void *opaque) +{ + struct n800_s *s = (struct n800_s *) opaque; + uint32_t buf; + + /* PRCM setup */ +#define omap_writel(addr, val) \ + buf = (val); \ + cpu_physical_memory_write(addr, &buf, sizeof(buf)) + + omap_writel(0x48008060, 0x41); /* PRCM_CLKSRC_CTRL */ + omap_writel(0x48008070, 1); /* PRCM_CLKOUT_CTRL */ + omap_writel(0x48008078, 0); /* PRCM_CLKEMUL_CTRL */ + omap_writel(0x48008090, 0); /* PRCM_VOLTSETUP */ + omap_writel(0x48008094, 0); /* PRCM_CLKSSETUP */ + omap_writel(0x48008098, 0); /* PRCM_POLCTRL */ + omap_writel(0x48008140, 2); /* CM_CLKSEL_MPU */ + omap_writel(0x48008148, 0); /* CM_CLKSTCTRL_MPU */ + omap_writel(0x48008158, 1); /* RM_RSTST_MPU */ + omap_writel(0x480081c8, 0x15); /* PM_WKDEP_MPU */ + omap_writel(0x480081d4, 0x1d4); /* PM_EVGENCTRL_MPU */ + omap_writel(0x480081d8, 0); /* PM_EVEGENONTIM_MPU */ + omap_writel(0x480081dc, 0); /* PM_EVEGENOFFTIM_MPU */ + omap_writel(0x480081e0, 0xc); /* PM_PWSTCTRL_MPU */ + omap_writel(0x48008200, 0x047e7ff7); /* CM_FCLKEN1_CORE */ + omap_writel(0x48008204, 0x00000004); /* CM_FCLKEN2_CORE */ + omap_writel(0x48008210, 0x047e7ff1); /* CM_ICLKEN1_CORE */ + omap_writel(0x48008214, 0x00000004); /* CM_ICLKEN2_CORE */ + omap_writel(0x4800821c, 0x00000000); /* CM_ICLKEN4_CORE */ + omap_writel(0x48008230, 0); /* CM_AUTOIDLE1_CORE */ + omap_writel(0x48008234, 0); /* CM_AUTOIDLE2_CORE */ + omap_writel(0x48008238, 7); /* CM_AUTOIDLE3_CORE */ + omap_writel(0x4800823c, 0); /* CM_AUTOIDLE4_CORE */ + omap_writel(0x48008240, 0x04360626); /* CM_CLKSEL1_CORE */ + omap_writel(0x48008244, 0x00000014); /* CM_CLKSEL2_CORE */ + omap_writel(0x48008248, 0); /* CM_CLKSTCTRL_CORE */ + omap_writel(0x48008300, 0x00000000); /* CM_FCLKEN_GFX */ + omap_writel(0x48008310, 0x00000000); /* CM_ICLKEN_GFX */ + omap_writel(0x48008340, 0x00000001); /* CM_CLKSEL_GFX */ + omap_writel(0x48008400, 0x00000004); /* CM_FCLKEN_WKUP */ + omap_writel(0x48008410, 0x00000004); /* CM_ICLKEN_WKUP */ + omap_writel(0x48008440, 0x00000000); /* CM_CLKSEL_WKUP */ + omap_writel(0x48008500, 0x000000cf); /* CM_CLKEN_PLL */ + omap_writel(0x48008530, 0x0000000c); /* CM_AUTOIDLE_PLL */ + omap_writel(0x48008540, /* CM_CLKSEL1_PLL */ + (0x78 << 12) | (6 << 8)); + omap_writel(0x48008544, 2); /* CM_CLKSEL2_PLL */ + + /* GPMC setup */ + n800_gpmc_init(s); + + /* Video setup */ + n800_dss_init(&s->blizzard); + + /* CPU setup */ + s->mpu->cpu->env.GE = 0x5; + + /* If the machine has a slided keyboard, open it */ + if (s->kbd) { + qemu_irq_raise(qdev_get_gpio_in(s->mpu->gpio, N810_SLIDE_GPIO)); + } +} + +#define OMAP_TAG_NOKIA_BT 0x4e01 +#define OMAP_TAG_WLAN_CX3110X 0x4e02 +#define OMAP_TAG_CBUS 0x4e03 +#define OMAP_TAG_EM_ASIC_BB5 0x4e04 + +static struct omap_gpiosw_info_s { + const char *name; + int line; + int type; +} n800_gpiosw_info[] = { + { + "bat_cover", N800_BAT_COVER_GPIO, + OMAP_GPIOSW_TYPE_COVER | OMAP_GPIOSW_INVERTED, + }, { + "cam_act", N800_CAM_ACT_GPIO, + OMAP_GPIOSW_TYPE_ACTIVITY, + }, { + "cam_turn", N800_CAM_TURN_GPIO, + OMAP_GPIOSW_TYPE_ACTIVITY | OMAP_GPIOSW_INVERTED, + }, { + "headphone", N8X0_HEADPHONE_GPIO, + OMAP_GPIOSW_TYPE_CONNECTION | OMAP_GPIOSW_INVERTED, + }, + { NULL } +}, n810_gpiosw_info[] = { + { + "gps_reset", N810_GPS_RESET_GPIO, + OMAP_GPIOSW_TYPE_ACTIVITY | OMAP_GPIOSW_OUTPUT, + }, { + "gps_wakeup", N810_GPS_WAKEUP_GPIO, + OMAP_GPIOSW_TYPE_ACTIVITY | OMAP_GPIOSW_OUTPUT, + }, { + "headphone", N8X0_HEADPHONE_GPIO, + OMAP_GPIOSW_TYPE_CONNECTION | OMAP_GPIOSW_INVERTED, + }, { + "kb_lock", N810_KB_LOCK_GPIO, + OMAP_GPIOSW_TYPE_COVER | OMAP_GPIOSW_INVERTED, + }, { + "sleepx_led", N810_SLEEPX_LED_GPIO, + OMAP_GPIOSW_TYPE_ACTIVITY | OMAP_GPIOSW_INVERTED | OMAP_GPIOSW_OUTPUT, + }, { + "slide", N810_SLIDE_GPIO, + OMAP_GPIOSW_TYPE_COVER | OMAP_GPIOSW_INVERTED, + }, + { NULL } +}; + +static struct omap_partition_info_s { + uint32_t offset; + uint32_t size; + int mask; + const char *name; +} n800_part_info[] = { + { 0x00000000, 0x00020000, 0x3, "bootloader" }, + { 0x00020000, 0x00060000, 0x0, "config" }, + { 0x00080000, 0x00200000, 0x0, "kernel" }, + { 0x00280000, 0x00200000, 0x3, "initfs" }, + { 0x00480000, 0x0fb80000, 0x3, "rootfs" }, + + { 0, 0, 0, NULL } +}, n810_part_info[] = { + { 0x00000000, 0x00020000, 0x3, "bootloader" }, + { 0x00020000, 0x00060000, 0x0, "config" }, + { 0x00080000, 0x00220000, 0x0, "kernel" }, + { 0x002a0000, 0x00400000, 0x0, "initfs" }, + { 0x006a0000, 0x0f960000, 0x0, "rootfs" }, + + { 0, 0, 0, NULL } +}; + +static bdaddr_t n8x0_bd_addr = {{ N8X0_BD_ADDR }}; + +static int n8x0_atag_setup(void *p, int model) +{ + uint8_t *b; + uint16_t *w; + uint32_t *l; + struct omap_gpiosw_info_s *gpiosw; + struct omap_partition_info_s *partition; + const char *tag; + + w = p; + + stw_p(w++, OMAP_TAG_UART); /* u16 tag */ + stw_p(w++, 4); /* u16 len */ + stw_p(w++, (1 << 2) | (1 << 1) | (1 << 0)); /* uint enabled_uarts */ + w++; + +#if 0 + stw_p(w++, OMAP_TAG_SERIAL_CONSOLE); /* u16 tag */ + stw_p(w++, 4); /* u16 len */ + stw_p(w++, XLDR_LL_UART + 1); /* u8 console_uart */ + stw_p(w++, 115200); /* u32 console_speed */ +#endif + + stw_p(w++, OMAP_TAG_LCD); /* u16 tag */ + stw_p(w++, 36); /* u16 len */ + strcpy((void *) w, "QEMU LCD panel"); /* char panel_name[16] */ + w += 8; + strcpy((void *) w, "blizzard"); /* char ctrl_name[16] */ + w += 8; + stw_p(w++, N810_BLIZZARD_RESET_GPIO); /* TODO: n800 s16 nreset_gpio */ + stw_p(w++, 24); /* u8 data_lines */ + + stw_p(w++, OMAP_TAG_CBUS); /* u16 tag */ + stw_p(w++, 8); /* u16 len */ + stw_p(w++, N8X0_CBUS_CLK_GPIO); /* s16 clk_gpio */ + stw_p(w++, N8X0_CBUS_DAT_GPIO); /* s16 dat_gpio */ + stw_p(w++, N8X0_CBUS_SEL_GPIO); /* s16 sel_gpio */ + w++; + + stw_p(w++, OMAP_TAG_EM_ASIC_BB5); /* u16 tag */ + stw_p(w++, 4); /* u16 len */ + stw_p(w++, N8X0_RETU_GPIO); /* s16 retu_irq_gpio */ + stw_p(w++, N8X0_TAHVO_GPIO); /* s16 tahvo_irq_gpio */ + + gpiosw = (model == 810) ? n810_gpiosw_info : n800_gpiosw_info; + for (; gpiosw->name; gpiosw++) { + stw_p(w++, OMAP_TAG_GPIO_SWITCH); /* u16 tag */ + stw_p(w++, 20); /* u16 len */ + strcpy((void *) w, gpiosw->name); /* char name[12] */ + w += 6; + stw_p(w++, gpiosw->line); /* u16 gpio */ + stw_p(w++, gpiosw->type); + stw_p(w++, 0); + stw_p(w++, 0); + } + + stw_p(w++, OMAP_TAG_NOKIA_BT); /* u16 tag */ + stw_p(w++, 12); /* u16 len */ + b = (void *) w; + stb_p(b++, 0x01); /* u8 chip_type (CSR) */ + stb_p(b++, N8X0_BT_WKUP_GPIO); /* u8 bt_wakeup_gpio */ + stb_p(b++, N8X0_BT_HOST_WKUP_GPIO); /* u8 host_wakeup_gpio */ + stb_p(b++, N8X0_BT_RESET_GPIO); /* u8 reset_gpio */ + stb_p(b++, BT_UART + 1); /* u8 bt_uart */ + memcpy(b, &n8x0_bd_addr, 6); /* u8 bd_addr[6] */ + b += 6; + stb_p(b++, 0x02); /* u8 bt_sysclk (38.4) */ + w = (void *) b; + + stw_p(w++, OMAP_TAG_WLAN_CX3110X); /* u16 tag */ + stw_p(w++, 8); /* u16 len */ + stw_p(w++, 0x25); /* u8 chip_type */ + stw_p(w++, N8X0_WLAN_PWR_GPIO); /* s16 power_gpio */ + stw_p(w++, N8X0_WLAN_IRQ_GPIO); /* s16 irq_gpio */ + stw_p(w++, -1); /* s16 spi_cs_gpio */ + + stw_p(w++, OMAP_TAG_MMC); /* u16 tag */ + stw_p(w++, 16); /* u16 len */ + if (model == 810) { + stw_p(w++, 0x23f); /* unsigned flags */ + stw_p(w++, -1); /* s16 power_pin */ + stw_p(w++, -1); /* s16 switch_pin */ + stw_p(w++, -1); /* s16 wp_pin */ + stw_p(w++, 0x240); /* unsigned flags */ + stw_p(w++, 0xc000); /* s16 power_pin */ + stw_p(w++, 0x0248); /* s16 switch_pin */ + stw_p(w++, 0xc000); /* s16 wp_pin */ + } else { + stw_p(w++, 0xf); /* unsigned flags */ + stw_p(w++, -1); /* s16 power_pin */ + stw_p(w++, -1); /* s16 switch_pin */ + stw_p(w++, -1); /* s16 wp_pin */ + stw_p(w++, 0); /* unsigned flags */ + stw_p(w++, 0); /* s16 power_pin */ + stw_p(w++, 0); /* s16 switch_pin */ + stw_p(w++, 0); /* s16 wp_pin */ + } + + stw_p(w++, OMAP_TAG_TEA5761); /* u16 tag */ + stw_p(w++, 4); /* u16 len */ + stw_p(w++, N8X0_TEA5761_CS_GPIO); /* u16 enable_gpio */ + w++; + + partition = (model == 810) ? n810_part_info : n800_part_info; + for (; partition->name; partition++) { + stw_p(w++, OMAP_TAG_PARTITION); /* u16 tag */ + stw_p(w++, 28); /* u16 len */ + strcpy((void *) w, partition->name); /* char name[16] */ + l = (void *) (w + 8); + stl_p(l++, partition->size); /* unsigned int size */ + stl_p(l++, partition->offset); /* unsigned int offset */ + stl_p(l++, partition->mask); /* unsigned int mask_flags */ + w = (void *) l; + } + + stw_p(w++, OMAP_TAG_BOOT_REASON); /* u16 tag */ + stw_p(w++, 12); /* u16 len */ +#if 0 + strcpy((void *) w, "por"); /* char reason_str[12] */ + strcpy((void *) w, "charger"); /* char reason_str[12] */ + strcpy((void *) w, "32wd_to"); /* char reason_str[12] */ + strcpy((void *) w, "sw_rst"); /* char reason_str[12] */ + strcpy((void *) w, "mbus"); /* char reason_str[12] */ + strcpy((void *) w, "unknown"); /* char reason_str[12] */ + strcpy((void *) w, "swdg_to"); /* char reason_str[12] */ + strcpy((void *) w, "sec_vio"); /* char reason_str[12] */ + strcpy((void *) w, "pwr_key"); /* char reason_str[12] */ + strcpy((void *) w, "rtc_alarm"); /* char reason_str[12] */ +#else + strcpy((void *) w, "pwr_key"); /* char reason_str[12] */ +#endif + w += 6; + + tag = (model == 810) ? "RX-44" : "RX-34"; + stw_p(w++, OMAP_TAG_VERSION_STR); /* u16 tag */ + stw_p(w++, 24); /* u16 len */ + strcpy((void *) w, "product"); /* char component[12] */ + w += 6; + strcpy((void *) w, tag); /* char version[12] */ + w += 6; + + stw_p(w++, OMAP_TAG_VERSION_STR); /* u16 tag */ + stw_p(w++, 24); /* u16 len */ + strcpy((void *) w, "hw-build"); /* char component[12] */ + w += 6; + strcpy((void *) w, "QEMU "); + pstrcat((void *) w, 12, qemu_get_version()); /* char version[12] */ + w += 6; + + tag = (model == 810) ? "1.1.10-qemu" : "1.1.6-qemu"; + stw_p(w++, OMAP_TAG_VERSION_STR); /* u16 tag */ + stw_p(w++, 24); /* u16 len */ + strcpy((void *) w, "nolo"); /* char component[12] */ + w += 6; + strcpy((void *) w, tag); /* char version[12] */ + w += 6; + + return (void *) w - p; +} + +static int n800_atag_setup(const struct arm_boot_info *info, void *p) +{ + return n8x0_atag_setup(p, 800); +} + +static int n810_atag_setup(const struct arm_boot_info *info, void *p) +{ + return n8x0_atag_setup(p, 810); +} + +static void n8x0_init(MachineState *machine, + struct arm_boot_info *binfo, int model) +{ + MemoryRegion *sysmem = get_system_memory(); + struct n800_s *s = (struct n800_s *) g_malloc0(sizeof(*s)); + int sdram_size = binfo->ram_size; + + s->mpu = omap2420_mpu_init(sysmem, sdram_size, machine->cpu_model); + + /* Setup peripherals + * + * Believed external peripherals layout in the N810: + * (spi bus 1) + * tsc2005 + * lcd_mipid + * (spi bus 2) + * Conexant cx3110x (WLAN) + * optional: pc2400m (WiMAX) + * (i2c bus 0) + * TLV320AIC33 (audio codec) + * TCM825x (camera by Toshiba) + * lp5521 (clever LEDs) + * tsl2563 (light sensor, hwmon, model 7, rev. 0) + * lm8323 (keypad, manf 00, rev 04) + * (i2c bus 1) + * tmp105 (temperature sensor, hwmon) + * menelaus (pm) + * (somewhere on i2c - maybe N800-only) + * tea5761 (FM tuner) + * (serial 0) + * GPS + * (some serial port) + * csr41814 (Bluetooth) + */ + n8x0_gpio_setup(s); + n8x0_nand_setup(s); + n8x0_i2c_setup(s); + if (model == 800) { + n800_tsc_kbd_setup(s); + } else if (model == 810) { + n810_tsc_setup(s); + n810_kbd_setup(s); + } + n8x0_spi_setup(s); + n8x0_dss_setup(s); + n8x0_cbus_setup(s); + n8x0_uart_setup(s); + if (usb_enabled()) { + n8x0_usb_setup(s); + } + + if (machine->kernel_filename) { + /* Or at the linux loader. */ + binfo->kernel_filename = machine->kernel_filename; + binfo->kernel_cmdline = machine->kernel_cmdline; + binfo->initrd_filename = machine->initrd_filename; + arm_load_kernel(s->mpu->cpu, binfo); + + qemu_register_reset(n8x0_boot_init, s); + } + + if (option_rom[0].name && + (machine->boot_order[0] == 'n' || !machine->kernel_filename)) { + uint8_t nolo_tags[0x10000]; + /* No, wait, better start at the ROM. */ + s->mpu->cpu->env.regs[15] = OMAP2_Q2_BASE + 0x400000; + + /* This is intended for loading the `secondary.bin' program from + * Nokia images (the NOLO bootloader). The entry point seems + * to be at OMAP2_Q2_BASE + 0x400000. + * + * The `2nd.bin' files contain some kind of earlier boot code and + * for them the entry point needs to be set to OMAP2_SRAM_BASE. + * + * The code above is for loading the `zImage' file from Nokia + * images. */ + load_image_targphys(option_rom[0].name, + OMAP2_Q2_BASE + 0x400000, + sdram_size - 0x400000); + + n800_setup_nolo_tags(nolo_tags); + cpu_physical_memory_write(OMAP2_SRAM_BASE, nolo_tags, 0x10000); + } +} + +static struct arm_boot_info n800_binfo = { + .loader_start = OMAP2_Q2_BASE, + /* Actually two chips of 0x4000000 bytes each */ + .ram_size = 0x08000000, + .board_id = 0x4f7, + .atag_board = n800_atag_setup, +}; + +static struct arm_boot_info n810_binfo = { + .loader_start = OMAP2_Q2_BASE, + /* Actually two chips of 0x4000000 bytes each */ + .ram_size = 0x08000000, + /* 0x60c and 0x6bf (WiMAX Edition) have been assigned but are not + * used by some older versions of the bootloader and 5555 is used + * instead (including versions that shipped with many devices). */ + .board_id = 0x60c, + .atag_board = n810_atag_setup, +}; + +static void n800_init(MachineState *machine) +{ + n8x0_init(machine, &n800_binfo, 800); +} + +static void n810_init(MachineState *machine) +{ + n8x0_init(machine, &n810_binfo, 810); +} + +static QEMUMachine n800_machine = { + .name = "n800", + .desc = "Nokia N800 tablet aka. RX-34 (OMAP2420)", + .init = n800_init, + .default_boot_order = "", +}; + +static QEMUMachine n810_machine = { + .name = "n810", + .desc = "Nokia N810 tablet aka. RX-44 (OMAP2420)", + .init = n810_init, + .default_boot_order = "", +}; + +static void nseries_machine_init(void) +{ + qemu_register_machine(&n800_machine); + qemu_register_machine(&n810_machine); +} + +machine_init(nseries_machine_init); diff --git a/qemu/hw/arm/omap1.c b/qemu/hw/arm/omap1.c new file mode 100644 index 000000000..de2b28925 --- /dev/null +++ b/qemu/hw/arm/omap1.c @@ -0,0 +1,4089 @@ +/* + * TI OMAP processors emulation. + * + * Copyright (C) 2006-2008 Andrzej Zaborowski <balrog@zabor.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw/boards.h" +#include "hw/hw.h" +#include "hw/arm/arm.h" +#include "hw/arm/omap.h" +#include "sysemu/sysemu.h" +#include "hw/arm/soc_dma.h" +#include "sysemu/block-backend.h" +#include "sysemu/blockdev.h" +#include "qemu/range.h" +#include "hw/sysbus.h" + +/* Should signal the TCMI/GPMC */ +uint32_t omap_badwidth_read8(void *opaque, hwaddr addr) +{ + uint8_t ret; + + OMAP_8B_REG(addr); + cpu_physical_memory_read(addr, &ret, 1); + return ret; +} + +void omap_badwidth_write8(void *opaque, hwaddr addr, + uint32_t value) +{ + uint8_t val8 = value; + + OMAP_8B_REG(addr); + cpu_physical_memory_write(addr, &val8, 1); +} + +uint32_t omap_badwidth_read16(void *opaque, hwaddr addr) +{ + uint16_t ret; + + OMAP_16B_REG(addr); + cpu_physical_memory_read(addr, &ret, 2); + return ret; +} + +void omap_badwidth_write16(void *opaque, hwaddr addr, + uint32_t value) +{ + uint16_t val16 = value; + + OMAP_16B_REG(addr); + cpu_physical_memory_write(addr, &val16, 2); +} + +uint32_t omap_badwidth_read32(void *opaque, hwaddr addr) +{ + uint32_t ret; + + OMAP_32B_REG(addr); + cpu_physical_memory_read(addr, &ret, 4); + return ret; +} + +void omap_badwidth_write32(void *opaque, hwaddr addr, + uint32_t value) +{ + OMAP_32B_REG(addr); + cpu_physical_memory_write(addr, &value, 4); +} + +/* MPU OS timers */ +struct omap_mpu_timer_s { + MemoryRegion iomem; + qemu_irq irq; + omap_clk clk; + uint32_t val; + int64_t time; + QEMUTimer *timer; + QEMUBH *tick; + int64_t rate; + int it_ena; + + int enable; + int ptv; + int ar; + int st; + uint32_t reset_val; +}; + +static inline uint32_t omap_timer_read(struct omap_mpu_timer_s *timer) +{ + uint64_t distance = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - timer->time; + + if (timer->st && timer->enable && timer->rate) + return timer->val - muldiv64(distance >> (timer->ptv + 1), + timer->rate, get_ticks_per_sec()); + else + return timer->val; +} + +static inline void omap_timer_sync(struct omap_mpu_timer_s *timer) +{ + timer->val = omap_timer_read(timer); + timer->time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); +} + +static inline void omap_timer_update(struct omap_mpu_timer_s *timer) +{ + int64_t expires; + + if (timer->enable && timer->st && timer->rate) { + timer->val = timer->reset_val; /* Should skip this on clk enable */ + expires = muldiv64((uint64_t) timer->val << (timer->ptv + 1), + get_ticks_per_sec(), timer->rate); + + /* If timer expiry would be sooner than in about 1 ms and + * auto-reload isn't set, then fire immediately. This is a hack + * to make systems like PalmOS run in acceptable time. PalmOS + * sets the interval to a very low value and polls the status bit + * in a busy loop when it wants to sleep just a couple of CPU + * ticks. */ + if (expires > (get_ticks_per_sec() >> 10) || timer->ar) + timer_mod(timer->timer, timer->time + expires); + else + qemu_bh_schedule(timer->tick); + } else + timer_del(timer->timer); +} + +static void omap_timer_fire(void *opaque) +{ + struct omap_mpu_timer_s *timer = opaque; + + if (!timer->ar) { + timer->val = 0; + timer->st = 0; + } + + if (timer->it_ena) + /* Edge-triggered irq */ + qemu_irq_pulse(timer->irq); +} + +static void omap_timer_tick(void *opaque) +{ + struct omap_mpu_timer_s *timer = (struct omap_mpu_timer_s *) opaque; + + omap_timer_sync(timer); + omap_timer_fire(timer); + omap_timer_update(timer); +} + +static void omap_timer_clk_update(void *opaque, int line, int on) +{ + struct omap_mpu_timer_s *timer = (struct omap_mpu_timer_s *) opaque; + + omap_timer_sync(timer); + timer->rate = on ? omap_clk_getrate(timer->clk) : 0; + omap_timer_update(timer); +} + +static void omap_timer_clk_setup(struct omap_mpu_timer_s *timer) +{ + omap_clk_adduser(timer->clk, + qemu_allocate_irq(omap_timer_clk_update, timer, 0)); + timer->rate = omap_clk_getrate(timer->clk); +} + +static uint64_t omap_mpu_timer_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_mpu_timer_s *s = (struct omap_mpu_timer_s *) opaque; + + if (size != 4) { + return omap_badwidth_read32(opaque, addr); + } + + switch (addr) { + case 0x00: /* CNTL_TIMER */ + return (s->enable << 5) | (s->ptv << 2) | (s->ar << 1) | s->st; + + case 0x04: /* LOAD_TIM */ + break; + + case 0x08: /* READ_TIM */ + return omap_timer_read(s); + } + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_mpu_timer_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_mpu_timer_s *s = (struct omap_mpu_timer_s *) opaque; + + if (size != 4) { + omap_badwidth_write32(opaque, addr, value); + return; + } + + switch (addr) { + case 0x00: /* CNTL_TIMER */ + omap_timer_sync(s); + s->enable = (value >> 5) & 1; + s->ptv = (value >> 2) & 7; + s->ar = (value >> 1) & 1; + s->st = value & 1; + omap_timer_update(s); + return; + + case 0x04: /* LOAD_TIM */ + s->reset_val = value; + return; + + case 0x08: /* READ_TIM */ + OMAP_RO_REG(addr); + break; + + default: + OMAP_BAD_REG(addr); + } +} + +static const MemoryRegionOps omap_mpu_timer_ops = { + .read = omap_mpu_timer_read, + .write = omap_mpu_timer_write, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void omap_mpu_timer_reset(struct omap_mpu_timer_s *s) +{ + timer_del(s->timer); + s->enable = 0; + s->reset_val = 31337; + s->val = 0; + s->ptv = 0; + s->ar = 0; + s->st = 0; + s->it_ena = 1; +} + +static struct omap_mpu_timer_s *omap_mpu_timer_init(MemoryRegion *system_memory, + hwaddr base, + qemu_irq irq, omap_clk clk) +{ + struct omap_mpu_timer_s *s = (struct omap_mpu_timer_s *) + g_malloc0(sizeof(struct omap_mpu_timer_s)); + + s->irq = irq; + s->clk = clk; + s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, omap_timer_tick, s); + s->tick = qemu_bh_new(omap_timer_fire, s); + omap_mpu_timer_reset(s); + omap_timer_clk_setup(s); + + memory_region_init_io(&s->iomem, NULL, &omap_mpu_timer_ops, s, + "omap-mpu-timer", 0x100); + + memory_region_add_subregion(system_memory, base, &s->iomem); + + return s; +} + +/* Watchdog timer */ +struct omap_watchdog_timer_s { + struct omap_mpu_timer_s timer; + MemoryRegion iomem; + uint8_t last_wr; + int mode; + int free; + int reset; +}; + +static uint64_t omap_wd_timer_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_watchdog_timer_s *s = (struct omap_watchdog_timer_s *) opaque; + + if (size != 2) { + return omap_badwidth_read16(opaque, addr); + } + + switch (addr) { + case 0x00: /* CNTL_TIMER */ + return (s->timer.ptv << 9) | (s->timer.ar << 8) | + (s->timer.st << 7) | (s->free << 1); + + case 0x04: /* READ_TIMER */ + return omap_timer_read(&s->timer); + + case 0x08: /* TIMER_MODE */ + return s->mode << 15; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_wd_timer_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_watchdog_timer_s *s = (struct omap_watchdog_timer_s *) opaque; + + if (size != 2) { + omap_badwidth_write16(opaque, addr, value); + return; + } + + switch (addr) { + case 0x00: /* CNTL_TIMER */ + omap_timer_sync(&s->timer); + s->timer.ptv = (value >> 9) & 7; + s->timer.ar = (value >> 8) & 1; + s->timer.st = (value >> 7) & 1; + s->free = (value >> 1) & 1; + omap_timer_update(&s->timer); + break; + + case 0x04: /* LOAD_TIMER */ + s->timer.reset_val = value & 0xffff; + break; + + case 0x08: /* TIMER_MODE */ + if (!s->mode && ((value >> 15) & 1)) + omap_clk_get(s->timer.clk); + s->mode |= (value >> 15) & 1; + if (s->last_wr == 0xf5) { + if ((value & 0xff) == 0xa0) { + if (s->mode) { + s->mode = 0; + omap_clk_put(s->timer.clk); + } + } else { + /* XXX: on T|E hardware somehow this has no effect, + * on Zire 71 it works as specified. */ + s->reset = 1; + qemu_system_reset_request(); + } + } + s->last_wr = value & 0xff; + break; + + default: + OMAP_BAD_REG(addr); + } +} + +static const MemoryRegionOps omap_wd_timer_ops = { + .read = omap_wd_timer_read, + .write = omap_wd_timer_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_wd_timer_reset(struct omap_watchdog_timer_s *s) +{ + timer_del(s->timer.timer); + if (!s->mode) + omap_clk_get(s->timer.clk); + s->mode = 1; + s->free = 1; + s->reset = 0; + s->timer.enable = 1; + s->timer.it_ena = 1; + s->timer.reset_val = 0xffff; + s->timer.val = 0; + s->timer.st = 0; + s->timer.ptv = 0; + s->timer.ar = 0; + omap_timer_update(&s->timer); +} + +static struct omap_watchdog_timer_s *omap_wd_timer_init(MemoryRegion *memory, + hwaddr base, + qemu_irq irq, omap_clk clk) +{ + struct omap_watchdog_timer_s *s = (struct omap_watchdog_timer_s *) + g_malloc0(sizeof(struct omap_watchdog_timer_s)); + + s->timer.irq = irq; + s->timer.clk = clk; + s->timer.timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, omap_timer_tick, &s->timer); + omap_wd_timer_reset(s); + omap_timer_clk_setup(&s->timer); + + memory_region_init_io(&s->iomem, NULL, &omap_wd_timer_ops, s, + "omap-wd-timer", 0x100); + memory_region_add_subregion(memory, base, &s->iomem); + + return s; +} + +/* 32-kHz timer */ +struct omap_32khz_timer_s { + struct omap_mpu_timer_s timer; + MemoryRegion iomem; +}; + +static uint64_t omap_os_timer_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_32khz_timer_s *s = (struct omap_32khz_timer_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + if (size != 4) { + return omap_badwidth_read32(opaque, addr); + } + + switch (offset) { + case 0x00: /* TVR */ + return s->timer.reset_val; + + case 0x04: /* TCR */ + return omap_timer_read(&s->timer); + + case 0x08: /* CR */ + return (s->timer.ar << 3) | (s->timer.it_ena << 2) | s->timer.st; + + default: + break; + } + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_os_timer_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_32khz_timer_s *s = (struct omap_32khz_timer_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + if (size != 4) { + omap_badwidth_write32(opaque, addr, value); + return; + } + + switch (offset) { + case 0x00: /* TVR */ + s->timer.reset_val = value & 0x00ffffff; + break; + + case 0x04: /* TCR */ + OMAP_RO_REG(addr); + break; + + case 0x08: /* CR */ + s->timer.ar = (value >> 3) & 1; + s->timer.it_ena = (value >> 2) & 1; + if (s->timer.st != (value & 1) || (value & 2)) { + omap_timer_sync(&s->timer); + s->timer.enable = value & 1; + s->timer.st = value & 1; + omap_timer_update(&s->timer); + } + break; + + default: + OMAP_BAD_REG(addr); + } +} + +static const MemoryRegionOps omap_os_timer_ops = { + .read = omap_os_timer_read, + .write = omap_os_timer_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_os_timer_reset(struct omap_32khz_timer_s *s) +{ + timer_del(s->timer.timer); + s->timer.enable = 0; + s->timer.it_ena = 0; + s->timer.reset_val = 0x00ffffff; + s->timer.val = 0; + s->timer.st = 0; + s->timer.ptv = 0; + s->timer.ar = 1; +} + +static struct omap_32khz_timer_s *omap_os_timer_init(MemoryRegion *memory, + hwaddr base, + qemu_irq irq, omap_clk clk) +{ + struct omap_32khz_timer_s *s = (struct omap_32khz_timer_s *) + g_malloc0(sizeof(struct omap_32khz_timer_s)); + + s->timer.irq = irq; + s->timer.clk = clk; + s->timer.timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, omap_timer_tick, &s->timer); + omap_os_timer_reset(s); + omap_timer_clk_setup(&s->timer); + + memory_region_init_io(&s->iomem, NULL, &omap_os_timer_ops, s, + "omap-os-timer", 0x800); + memory_region_add_subregion(memory, base, &s->iomem); + + return s; +} + +/* Ultra Low-Power Device Module */ +static uint64_t omap_ulpd_pm_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + uint16_t ret; + + if (size != 2) { + return omap_badwidth_read16(opaque, addr); + } + + switch (addr) { + case 0x14: /* IT_STATUS */ + ret = s->ulpd_pm_regs[addr >> 2]; + s->ulpd_pm_regs[addr >> 2] = 0; + qemu_irq_lower(qdev_get_gpio_in(s->ih[1], OMAP_INT_GAUGE_32K)); + return ret; + + case 0x18: /* Reserved */ + case 0x1c: /* Reserved */ + case 0x20: /* Reserved */ + case 0x28: /* Reserved */ + case 0x2c: /* Reserved */ + OMAP_BAD_REG(addr); + /* fall through */ + case 0x00: /* COUNTER_32_LSB */ + case 0x04: /* COUNTER_32_MSB */ + case 0x08: /* COUNTER_HIGH_FREQ_LSB */ + case 0x0c: /* COUNTER_HIGH_FREQ_MSB */ + case 0x10: /* GAUGING_CTRL */ + case 0x24: /* SETUP_ANALOG_CELL3_ULPD1 */ + case 0x30: /* CLOCK_CTRL */ + case 0x34: /* SOFT_REQ */ + case 0x38: /* COUNTER_32_FIQ */ + case 0x3c: /* DPLL_CTRL */ + case 0x40: /* STATUS_REQ */ + /* XXX: check clk::usecount state for every clock */ + case 0x48: /* LOCL_TIME */ + case 0x4c: /* APLL_CTRL */ + case 0x50: /* POWER_CTRL */ + return s->ulpd_pm_regs[addr >> 2]; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static inline void omap_ulpd_clk_update(struct omap_mpu_state_s *s, + uint16_t diff, uint16_t value) +{ + if (diff & (1 << 4)) /* USB_MCLK_EN */ + omap_clk_onoff(omap_findclk(s, "usb_clk0"), (value >> 4) & 1); + if (diff & (1 << 5)) /* DIS_USB_PVCI_CLK */ + omap_clk_onoff(omap_findclk(s, "usb_w2fc_ck"), (~value >> 5) & 1); +} + +static inline void omap_ulpd_req_update(struct omap_mpu_state_s *s, + uint16_t diff, uint16_t value) +{ + if (diff & (1 << 0)) /* SOFT_DPLL_REQ */ + omap_clk_canidle(omap_findclk(s, "dpll4"), (~value >> 0) & 1); + if (diff & (1 << 1)) /* SOFT_COM_REQ */ + omap_clk_canidle(omap_findclk(s, "com_mclk_out"), (~value >> 1) & 1); + if (diff & (1 << 2)) /* SOFT_SDW_REQ */ + omap_clk_canidle(omap_findclk(s, "bt_mclk_out"), (~value >> 2) & 1); + if (diff & (1 << 3)) /* SOFT_USB_REQ */ + omap_clk_canidle(omap_findclk(s, "usb_clk0"), (~value >> 3) & 1); +} + +static void omap_ulpd_pm_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + int64_t now, ticks; + int div, mult; + static const int bypass_div[4] = { 1, 2, 4, 4 }; + uint16_t diff; + + if (size != 2) { + omap_badwidth_write16(opaque, addr, value); + return; + } + + switch (addr) { + case 0x00: /* COUNTER_32_LSB */ + case 0x04: /* COUNTER_32_MSB */ + case 0x08: /* COUNTER_HIGH_FREQ_LSB */ + case 0x0c: /* COUNTER_HIGH_FREQ_MSB */ + case 0x14: /* IT_STATUS */ + case 0x40: /* STATUS_REQ */ + OMAP_RO_REG(addr); + break; + + case 0x10: /* GAUGING_CTRL */ + /* Bits 0 and 1 seem to be confused in the OMAP 310 TRM */ + if ((s->ulpd_pm_regs[addr >> 2] ^ value) & 1) { + now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + + if (value & 1) + s->ulpd_gauge_start = now; + else { + now -= s->ulpd_gauge_start; + + /* 32-kHz ticks */ + ticks = muldiv64(now, 32768, get_ticks_per_sec()); + s->ulpd_pm_regs[0x00 >> 2] = (ticks >> 0) & 0xffff; + s->ulpd_pm_regs[0x04 >> 2] = (ticks >> 16) & 0xffff; + if (ticks >> 32) /* OVERFLOW_32K */ + s->ulpd_pm_regs[0x14 >> 2] |= 1 << 2; + + /* High frequency ticks */ + ticks = muldiv64(now, 12000000, get_ticks_per_sec()); + s->ulpd_pm_regs[0x08 >> 2] = (ticks >> 0) & 0xffff; + s->ulpd_pm_regs[0x0c >> 2] = (ticks >> 16) & 0xffff; + if (ticks >> 32) /* OVERFLOW_HI_FREQ */ + s->ulpd_pm_regs[0x14 >> 2] |= 1 << 1; + + s->ulpd_pm_regs[0x14 >> 2] |= 1 << 0; /* IT_GAUGING */ + qemu_irq_raise(qdev_get_gpio_in(s->ih[1], OMAP_INT_GAUGE_32K)); + } + } + s->ulpd_pm_regs[addr >> 2] = value; + break; + + case 0x18: /* Reserved */ + case 0x1c: /* Reserved */ + case 0x20: /* Reserved */ + case 0x28: /* Reserved */ + case 0x2c: /* Reserved */ + OMAP_BAD_REG(addr); + /* fall through */ + case 0x24: /* SETUP_ANALOG_CELL3_ULPD1 */ + case 0x38: /* COUNTER_32_FIQ */ + case 0x48: /* LOCL_TIME */ + case 0x50: /* POWER_CTRL */ + s->ulpd_pm_regs[addr >> 2] = value; + break; + + case 0x30: /* CLOCK_CTRL */ + diff = s->ulpd_pm_regs[addr >> 2] ^ value; + s->ulpd_pm_regs[addr >> 2] = value & 0x3f; + omap_ulpd_clk_update(s, diff, value); + break; + + case 0x34: /* SOFT_REQ */ + diff = s->ulpd_pm_regs[addr >> 2] ^ value; + s->ulpd_pm_regs[addr >> 2] = value & 0x1f; + omap_ulpd_req_update(s, diff, value); + break; + + case 0x3c: /* DPLL_CTRL */ + /* XXX: OMAP310 TRM claims bit 3 is PLL_ENABLE, and bit 4 is + * omitted altogether, probably a typo. */ + /* This register has identical semantics with DPLL(1:3) control + * registers, see omap_dpll_write() */ + diff = s->ulpd_pm_regs[addr >> 2] & value; + s->ulpd_pm_regs[addr >> 2] = value & 0x2fff; + if (diff & (0x3ff << 2)) { + if (value & (1 << 4)) { /* PLL_ENABLE */ + div = ((value >> 5) & 3) + 1; /* PLL_DIV */ + mult = MIN((value >> 7) & 0x1f, 1); /* PLL_MULT */ + } else { + div = bypass_div[((value >> 2) & 3)]; /* BYPASS_DIV */ + mult = 1; + } + omap_clk_setrate(omap_findclk(s, "dpll4"), div, mult); + } + + /* Enter the desired mode. */ + s->ulpd_pm_regs[addr >> 2] = + (s->ulpd_pm_regs[addr >> 2] & 0xfffe) | + ((s->ulpd_pm_regs[addr >> 2] >> 4) & 1); + + /* Act as if the lock is restored. */ + s->ulpd_pm_regs[addr >> 2] |= 2; + break; + + case 0x4c: /* APLL_CTRL */ + diff = s->ulpd_pm_regs[addr >> 2] & value; + s->ulpd_pm_regs[addr >> 2] = value & 0xf; + if (diff & (1 << 0)) /* APLL_NDPLL_SWITCH */ + omap_clk_reparent(omap_findclk(s, "ck_48m"), omap_findclk(s, + (value & (1 << 0)) ? "apll" : "dpll4")); + break; + + default: + OMAP_BAD_REG(addr); + } +} + +static const MemoryRegionOps omap_ulpd_pm_ops = { + .read = omap_ulpd_pm_read, + .write = omap_ulpd_pm_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_ulpd_pm_reset(struct omap_mpu_state_s *mpu) +{ + mpu->ulpd_pm_regs[0x00 >> 2] = 0x0001; + mpu->ulpd_pm_regs[0x04 >> 2] = 0x0000; + mpu->ulpd_pm_regs[0x08 >> 2] = 0x0001; + mpu->ulpd_pm_regs[0x0c >> 2] = 0x0000; + mpu->ulpd_pm_regs[0x10 >> 2] = 0x0000; + mpu->ulpd_pm_regs[0x18 >> 2] = 0x01; + mpu->ulpd_pm_regs[0x1c >> 2] = 0x01; + mpu->ulpd_pm_regs[0x20 >> 2] = 0x01; + mpu->ulpd_pm_regs[0x24 >> 2] = 0x03ff; + mpu->ulpd_pm_regs[0x28 >> 2] = 0x01; + mpu->ulpd_pm_regs[0x2c >> 2] = 0x01; + omap_ulpd_clk_update(mpu, mpu->ulpd_pm_regs[0x30 >> 2], 0x0000); + mpu->ulpd_pm_regs[0x30 >> 2] = 0x0000; + omap_ulpd_req_update(mpu, mpu->ulpd_pm_regs[0x34 >> 2], 0x0000); + mpu->ulpd_pm_regs[0x34 >> 2] = 0x0000; + mpu->ulpd_pm_regs[0x38 >> 2] = 0x0001; + mpu->ulpd_pm_regs[0x3c >> 2] = 0x2211; + mpu->ulpd_pm_regs[0x40 >> 2] = 0x0000; /* FIXME: dump a real STATUS_REQ */ + mpu->ulpd_pm_regs[0x48 >> 2] = 0x960; + mpu->ulpd_pm_regs[0x4c >> 2] = 0x08; + mpu->ulpd_pm_regs[0x50 >> 2] = 0x08; + omap_clk_setrate(omap_findclk(mpu, "dpll4"), 1, 4); + omap_clk_reparent(omap_findclk(mpu, "ck_48m"), omap_findclk(mpu, "dpll4")); +} + +static void omap_ulpd_pm_init(MemoryRegion *system_memory, + hwaddr base, + struct omap_mpu_state_s *mpu) +{ + memory_region_init_io(&mpu->ulpd_pm_iomem, NULL, &omap_ulpd_pm_ops, mpu, + "omap-ulpd-pm", 0x800); + memory_region_add_subregion(system_memory, base, &mpu->ulpd_pm_iomem); + omap_ulpd_pm_reset(mpu); +} + +/* OMAP Pin Configuration */ +static uint64_t omap_pin_cfg_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + + if (size != 4) { + return omap_badwidth_read32(opaque, addr); + } + + switch (addr) { + case 0x00: /* FUNC_MUX_CTRL_0 */ + case 0x04: /* FUNC_MUX_CTRL_1 */ + case 0x08: /* FUNC_MUX_CTRL_2 */ + return s->func_mux_ctrl[addr >> 2]; + + case 0x0c: /* COMP_MODE_CTRL_0 */ + return s->comp_mode_ctrl[0]; + + case 0x10: /* FUNC_MUX_CTRL_3 */ + case 0x14: /* FUNC_MUX_CTRL_4 */ + case 0x18: /* FUNC_MUX_CTRL_5 */ + case 0x1c: /* FUNC_MUX_CTRL_6 */ + case 0x20: /* FUNC_MUX_CTRL_7 */ + case 0x24: /* FUNC_MUX_CTRL_8 */ + case 0x28: /* FUNC_MUX_CTRL_9 */ + case 0x2c: /* FUNC_MUX_CTRL_A */ + case 0x30: /* FUNC_MUX_CTRL_B */ + case 0x34: /* FUNC_MUX_CTRL_C */ + case 0x38: /* FUNC_MUX_CTRL_D */ + return s->func_mux_ctrl[(addr >> 2) - 1]; + + case 0x40: /* PULL_DWN_CTRL_0 */ + case 0x44: /* PULL_DWN_CTRL_1 */ + case 0x48: /* PULL_DWN_CTRL_2 */ + case 0x4c: /* PULL_DWN_CTRL_3 */ + return s->pull_dwn_ctrl[(addr & 0xf) >> 2]; + + case 0x50: /* GATE_INH_CTRL_0 */ + return s->gate_inh_ctrl[0]; + + case 0x60: /* VOLTAGE_CTRL_0 */ + return s->voltage_ctrl[0]; + + case 0x70: /* TEST_DBG_CTRL_0 */ + return s->test_dbg_ctrl[0]; + + case 0x80: /* MOD_CONF_CTRL_0 */ + return s->mod_conf_ctrl[0]; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static inline void omap_pin_funcmux0_update(struct omap_mpu_state_s *s, + uint32_t diff, uint32_t value) +{ + if (s->compat1509) { + if (diff & (1 << 9)) /* BLUETOOTH */ + omap_clk_onoff(omap_findclk(s, "bt_mclk_out"), + (~value >> 9) & 1); + if (diff & (1 << 7)) /* USB.CLKO */ + omap_clk_onoff(omap_findclk(s, "usb.clko"), + (value >> 7) & 1); + } +} + +static inline void omap_pin_funcmux1_update(struct omap_mpu_state_s *s, + uint32_t diff, uint32_t value) +{ + if (s->compat1509) { + if (diff & (1U << 31)) { + /* MCBSP3_CLK_HIZ_DI */ + omap_clk_onoff(omap_findclk(s, "mcbsp3.clkx"), (value >> 31) & 1); + } + if (diff & (1 << 1)) { + /* CLK32K */ + omap_clk_onoff(omap_findclk(s, "clk32k_out"), (~value >> 1) & 1); + } + } +} + +static inline void omap_pin_modconf1_update(struct omap_mpu_state_s *s, + uint32_t diff, uint32_t value) +{ + if (diff & (1U << 31)) { + /* CONF_MOD_UART3_CLK_MODE_R */ + omap_clk_reparent(omap_findclk(s, "uart3_ck"), + omap_findclk(s, ((value >> 31) & 1) ? + "ck_48m" : "armper_ck")); + } + if (diff & (1 << 30)) /* CONF_MOD_UART2_CLK_MODE_R */ + omap_clk_reparent(omap_findclk(s, "uart2_ck"), + omap_findclk(s, ((value >> 30) & 1) ? + "ck_48m" : "armper_ck")); + if (diff & (1 << 29)) /* CONF_MOD_UART1_CLK_MODE_R */ + omap_clk_reparent(omap_findclk(s, "uart1_ck"), + omap_findclk(s, ((value >> 29) & 1) ? + "ck_48m" : "armper_ck")); + if (diff & (1 << 23)) /* CONF_MOD_MMC_SD_CLK_REQ_R */ + omap_clk_reparent(omap_findclk(s, "mmc_ck"), + omap_findclk(s, ((value >> 23) & 1) ? + "ck_48m" : "armper_ck")); + if (diff & (1 << 12)) /* CONF_MOD_COM_MCLK_12_48_S */ + omap_clk_reparent(omap_findclk(s, "com_mclk_out"), + omap_findclk(s, ((value >> 12) & 1) ? + "ck_48m" : "armper_ck")); + if (diff & (1 << 9)) /* CONF_MOD_USB_HOST_HHC_UHO */ + omap_clk_onoff(omap_findclk(s, "usb_hhc_ck"), (value >> 9) & 1); +} + +static void omap_pin_cfg_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + uint32_t diff; + + if (size != 4) { + omap_badwidth_write32(opaque, addr, value); + return; + } + + switch (addr) { + case 0x00: /* FUNC_MUX_CTRL_0 */ + diff = s->func_mux_ctrl[addr >> 2] ^ value; + s->func_mux_ctrl[addr >> 2] = value; + omap_pin_funcmux0_update(s, diff, value); + return; + + case 0x04: /* FUNC_MUX_CTRL_1 */ + diff = s->func_mux_ctrl[addr >> 2] ^ value; + s->func_mux_ctrl[addr >> 2] = value; + omap_pin_funcmux1_update(s, diff, value); + return; + + case 0x08: /* FUNC_MUX_CTRL_2 */ + s->func_mux_ctrl[addr >> 2] = value; + return; + + case 0x0c: /* COMP_MODE_CTRL_0 */ + s->comp_mode_ctrl[0] = value; + s->compat1509 = (value != 0x0000eaef); + omap_pin_funcmux0_update(s, ~0, s->func_mux_ctrl[0]); + omap_pin_funcmux1_update(s, ~0, s->func_mux_ctrl[1]); + return; + + case 0x10: /* FUNC_MUX_CTRL_3 */ + case 0x14: /* FUNC_MUX_CTRL_4 */ + case 0x18: /* FUNC_MUX_CTRL_5 */ + case 0x1c: /* FUNC_MUX_CTRL_6 */ + case 0x20: /* FUNC_MUX_CTRL_7 */ + case 0x24: /* FUNC_MUX_CTRL_8 */ + case 0x28: /* FUNC_MUX_CTRL_9 */ + case 0x2c: /* FUNC_MUX_CTRL_A */ + case 0x30: /* FUNC_MUX_CTRL_B */ + case 0x34: /* FUNC_MUX_CTRL_C */ + case 0x38: /* FUNC_MUX_CTRL_D */ + s->func_mux_ctrl[(addr >> 2) - 1] = value; + return; + + case 0x40: /* PULL_DWN_CTRL_0 */ + case 0x44: /* PULL_DWN_CTRL_1 */ + case 0x48: /* PULL_DWN_CTRL_2 */ + case 0x4c: /* PULL_DWN_CTRL_3 */ + s->pull_dwn_ctrl[(addr & 0xf) >> 2] = value; + return; + + case 0x50: /* GATE_INH_CTRL_0 */ + s->gate_inh_ctrl[0] = value; + return; + + case 0x60: /* VOLTAGE_CTRL_0 */ + s->voltage_ctrl[0] = value; + return; + + case 0x70: /* TEST_DBG_CTRL_0 */ + s->test_dbg_ctrl[0] = value; + return; + + case 0x80: /* MOD_CONF_CTRL_0 */ + diff = s->mod_conf_ctrl[0] ^ value; + s->mod_conf_ctrl[0] = value; + omap_pin_modconf1_update(s, diff, value); + return; + + default: + OMAP_BAD_REG(addr); + } +} + +static const MemoryRegionOps omap_pin_cfg_ops = { + .read = omap_pin_cfg_read, + .write = omap_pin_cfg_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_pin_cfg_reset(struct omap_mpu_state_s *mpu) +{ + /* Start in Compatibility Mode. */ + mpu->compat1509 = 1; + omap_pin_funcmux0_update(mpu, mpu->func_mux_ctrl[0], 0); + omap_pin_funcmux1_update(mpu, mpu->func_mux_ctrl[1], 0); + omap_pin_modconf1_update(mpu, mpu->mod_conf_ctrl[0], 0); + memset(mpu->func_mux_ctrl, 0, sizeof(mpu->func_mux_ctrl)); + memset(mpu->comp_mode_ctrl, 0, sizeof(mpu->comp_mode_ctrl)); + memset(mpu->pull_dwn_ctrl, 0, sizeof(mpu->pull_dwn_ctrl)); + memset(mpu->gate_inh_ctrl, 0, sizeof(mpu->gate_inh_ctrl)); + memset(mpu->voltage_ctrl, 0, sizeof(mpu->voltage_ctrl)); + memset(mpu->test_dbg_ctrl, 0, sizeof(mpu->test_dbg_ctrl)); + memset(mpu->mod_conf_ctrl, 0, sizeof(mpu->mod_conf_ctrl)); +} + +static void omap_pin_cfg_init(MemoryRegion *system_memory, + hwaddr base, + struct omap_mpu_state_s *mpu) +{ + memory_region_init_io(&mpu->pin_cfg_iomem, NULL, &omap_pin_cfg_ops, mpu, + "omap-pin-cfg", 0x800); + memory_region_add_subregion(system_memory, base, &mpu->pin_cfg_iomem); + omap_pin_cfg_reset(mpu); +} + +/* Device Identification, Die Identification */ +static uint64_t omap_id_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + + if (size != 4) { + return omap_badwidth_read32(opaque, addr); + } + + switch (addr) { + case 0xfffe1800: /* DIE_ID_LSB */ + return 0xc9581f0e; + case 0xfffe1804: /* DIE_ID_MSB */ + return 0xa8858bfa; + + case 0xfffe2000: /* PRODUCT_ID_LSB */ + return 0x00aaaafc; + case 0xfffe2004: /* PRODUCT_ID_MSB */ + return 0xcafeb574; + + case 0xfffed400: /* JTAG_ID_LSB */ + switch (s->mpu_model) { + case omap310: + return 0x03310315; + case omap1510: + return 0x03310115; + default: + hw_error("%s: bad mpu model\n", __FUNCTION__); + } + break; + + case 0xfffed404: /* JTAG_ID_MSB */ + switch (s->mpu_model) { + case omap310: + return 0xfb57402f; + case omap1510: + return 0xfb47002f; + default: + hw_error("%s: bad mpu model\n", __FUNCTION__); + } + break; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_id_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + if (size != 4) { + omap_badwidth_write32(opaque, addr, value); + return; + } + + OMAP_BAD_REG(addr); +} + +static const MemoryRegionOps omap_id_ops = { + .read = omap_id_read, + .write = omap_id_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_id_init(MemoryRegion *memory, struct omap_mpu_state_s *mpu) +{ + memory_region_init_io(&mpu->id_iomem, NULL, &omap_id_ops, mpu, + "omap-id", 0x100000000ULL); + memory_region_init_alias(&mpu->id_iomem_e18, NULL, "omap-id-e18", &mpu->id_iomem, + 0xfffe1800, 0x800); + memory_region_add_subregion(memory, 0xfffe1800, &mpu->id_iomem_e18); + memory_region_init_alias(&mpu->id_iomem_ed4, NULL, "omap-id-ed4", &mpu->id_iomem, + 0xfffed400, 0x100); + memory_region_add_subregion(memory, 0xfffed400, &mpu->id_iomem_ed4); + if (!cpu_is_omap15xx(mpu)) { + memory_region_init_alias(&mpu->id_iomem_ed4, NULL, "omap-id-e20", + &mpu->id_iomem, 0xfffe2000, 0x800); + memory_region_add_subregion(memory, 0xfffe2000, &mpu->id_iomem_e20); + } +} + +/* MPUI Control (Dummy) */ +static uint64_t omap_mpui_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + + if (size != 4) { + return omap_badwidth_read32(opaque, addr); + } + + switch (addr) { + case 0x00: /* CTRL */ + return s->mpui_ctrl; + case 0x04: /* DEBUG_ADDR */ + return 0x01ffffff; + case 0x08: /* DEBUG_DATA */ + return 0xffffffff; + case 0x0c: /* DEBUG_FLAG */ + return 0x00000800; + case 0x10: /* STATUS */ + return 0x00000000; + + /* Not in OMAP310 */ + case 0x14: /* DSP_STATUS */ + case 0x18: /* DSP_BOOT_CONFIG */ + return 0x00000000; + case 0x1c: /* DSP_MPUI_CONFIG */ + return 0x0000ffff; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_mpui_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + + if (size != 4) { + omap_badwidth_write32(opaque, addr, value); + return; + } + + switch (addr) { + case 0x00: /* CTRL */ + s->mpui_ctrl = value & 0x007fffff; + break; + + case 0x04: /* DEBUG_ADDR */ + case 0x08: /* DEBUG_DATA */ + case 0x0c: /* DEBUG_FLAG */ + case 0x10: /* STATUS */ + /* Not in OMAP310 */ + case 0x14: /* DSP_STATUS */ + OMAP_RO_REG(addr); + break; + case 0x18: /* DSP_BOOT_CONFIG */ + case 0x1c: /* DSP_MPUI_CONFIG */ + break; + + default: + OMAP_BAD_REG(addr); + } +} + +static const MemoryRegionOps omap_mpui_ops = { + .read = omap_mpui_read, + .write = omap_mpui_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_mpui_reset(struct omap_mpu_state_s *s) +{ + s->mpui_ctrl = 0x0003ff1b; +} + +static void omap_mpui_init(MemoryRegion *memory, hwaddr base, + struct omap_mpu_state_s *mpu) +{ + memory_region_init_io(&mpu->mpui_iomem, NULL, &omap_mpui_ops, mpu, + "omap-mpui", 0x100); + memory_region_add_subregion(memory, base, &mpu->mpui_iomem); + + omap_mpui_reset(mpu); +} + +/* TIPB Bridges */ +struct omap_tipb_bridge_s { + qemu_irq abort; + MemoryRegion iomem; + + int width_intr; + uint16_t control; + uint16_t alloc; + uint16_t buffer; + uint16_t enh_control; +}; + +static uint64_t omap_tipb_bridge_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_tipb_bridge_s *s = (struct omap_tipb_bridge_s *) opaque; + + if (size < 2) { + return omap_badwidth_read16(opaque, addr); + } + + switch (addr) { + case 0x00: /* TIPB_CNTL */ + return s->control; + case 0x04: /* TIPB_BUS_ALLOC */ + return s->alloc; + case 0x08: /* MPU_TIPB_CNTL */ + return s->buffer; + case 0x0c: /* ENHANCED_TIPB_CNTL */ + return s->enh_control; + case 0x10: /* ADDRESS_DBG */ + case 0x14: /* DATA_DEBUG_LOW */ + case 0x18: /* DATA_DEBUG_HIGH */ + return 0xffff; + case 0x1c: /* DEBUG_CNTR_SIG */ + return 0x00f8; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_tipb_bridge_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_tipb_bridge_s *s = (struct omap_tipb_bridge_s *) opaque; + + if (size < 2) { + omap_badwidth_write16(opaque, addr, value); + return; + } + + switch (addr) { + case 0x00: /* TIPB_CNTL */ + s->control = value & 0xffff; + break; + + case 0x04: /* TIPB_BUS_ALLOC */ + s->alloc = value & 0x003f; + break; + + case 0x08: /* MPU_TIPB_CNTL */ + s->buffer = value & 0x0003; + break; + + case 0x0c: /* ENHANCED_TIPB_CNTL */ + s->width_intr = !(value & 2); + s->enh_control = value & 0x000f; + break; + + case 0x10: /* ADDRESS_DBG */ + case 0x14: /* DATA_DEBUG_LOW */ + case 0x18: /* DATA_DEBUG_HIGH */ + case 0x1c: /* DEBUG_CNTR_SIG */ + OMAP_RO_REG(addr); + break; + + default: + OMAP_BAD_REG(addr); + } +} + +static const MemoryRegionOps omap_tipb_bridge_ops = { + .read = omap_tipb_bridge_read, + .write = omap_tipb_bridge_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_tipb_bridge_reset(struct omap_tipb_bridge_s *s) +{ + s->control = 0xffff; + s->alloc = 0x0009; + s->buffer = 0x0000; + s->enh_control = 0x000f; +} + +static struct omap_tipb_bridge_s *omap_tipb_bridge_init( + MemoryRegion *memory, hwaddr base, + qemu_irq abort_irq, omap_clk clk) +{ + struct omap_tipb_bridge_s *s = (struct omap_tipb_bridge_s *) + g_malloc0(sizeof(struct omap_tipb_bridge_s)); + + s->abort = abort_irq; + omap_tipb_bridge_reset(s); + + memory_region_init_io(&s->iomem, NULL, &omap_tipb_bridge_ops, s, + "omap-tipb-bridge", 0x100); + memory_region_add_subregion(memory, base, &s->iomem); + + return s; +} + +/* Dummy Traffic Controller's Memory Interface */ +static uint64_t omap_tcmi_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + uint32_t ret; + + if (size != 4) { + return omap_badwidth_read32(opaque, addr); + } + + switch (addr) { + case 0x00: /* IMIF_PRIO */ + case 0x04: /* EMIFS_PRIO */ + case 0x08: /* EMIFF_PRIO */ + case 0x0c: /* EMIFS_CONFIG */ + case 0x10: /* EMIFS_CS0_CONFIG */ + case 0x14: /* EMIFS_CS1_CONFIG */ + case 0x18: /* EMIFS_CS2_CONFIG */ + case 0x1c: /* EMIFS_CS3_CONFIG */ + case 0x24: /* EMIFF_MRS */ + case 0x28: /* TIMEOUT1 */ + case 0x2c: /* TIMEOUT2 */ + case 0x30: /* TIMEOUT3 */ + case 0x3c: /* EMIFF_SDRAM_CONFIG_2 */ + case 0x40: /* EMIFS_CFG_DYN_WAIT */ + return s->tcmi_regs[addr >> 2]; + + case 0x20: /* EMIFF_SDRAM_CONFIG */ + ret = s->tcmi_regs[addr >> 2]; + s->tcmi_regs[addr >> 2] &= ~1; /* XXX: Clear SLRF on SDRAM access */ + /* XXX: We can try using the VGA_DIRTY flag for this */ + return ret; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_tcmi_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + + if (size != 4) { + omap_badwidth_write32(opaque, addr, value); + return; + } + + switch (addr) { + case 0x00: /* IMIF_PRIO */ + case 0x04: /* EMIFS_PRIO */ + case 0x08: /* EMIFF_PRIO */ + case 0x10: /* EMIFS_CS0_CONFIG */ + case 0x14: /* EMIFS_CS1_CONFIG */ + case 0x18: /* EMIFS_CS2_CONFIG */ + case 0x1c: /* EMIFS_CS3_CONFIG */ + case 0x20: /* EMIFF_SDRAM_CONFIG */ + case 0x24: /* EMIFF_MRS */ + case 0x28: /* TIMEOUT1 */ + case 0x2c: /* TIMEOUT2 */ + case 0x30: /* TIMEOUT3 */ + case 0x3c: /* EMIFF_SDRAM_CONFIG_2 */ + case 0x40: /* EMIFS_CFG_DYN_WAIT */ + s->tcmi_regs[addr >> 2] = value; + break; + case 0x0c: /* EMIFS_CONFIG */ + s->tcmi_regs[addr >> 2] = (value & 0xf) | (1 << 4); + break; + + default: + OMAP_BAD_REG(addr); + } +} + +static const MemoryRegionOps omap_tcmi_ops = { + .read = omap_tcmi_read, + .write = omap_tcmi_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_tcmi_reset(struct omap_mpu_state_s *mpu) +{ + mpu->tcmi_regs[0x00 >> 2] = 0x00000000; + mpu->tcmi_regs[0x04 >> 2] = 0x00000000; + mpu->tcmi_regs[0x08 >> 2] = 0x00000000; + mpu->tcmi_regs[0x0c >> 2] = 0x00000010; + mpu->tcmi_regs[0x10 >> 2] = 0x0010fffb; + mpu->tcmi_regs[0x14 >> 2] = 0x0010fffb; + mpu->tcmi_regs[0x18 >> 2] = 0x0010fffb; + mpu->tcmi_regs[0x1c >> 2] = 0x0010fffb; + mpu->tcmi_regs[0x20 >> 2] = 0x00618800; + mpu->tcmi_regs[0x24 >> 2] = 0x00000037; + mpu->tcmi_regs[0x28 >> 2] = 0x00000000; + mpu->tcmi_regs[0x2c >> 2] = 0x00000000; + mpu->tcmi_regs[0x30 >> 2] = 0x00000000; + mpu->tcmi_regs[0x3c >> 2] = 0x00000003; + mpu->tcmi_regs[0x40 >> 2] = 0x00000000; +} + +static void omap_tcmi_init(MemoryRegion *memory, hwaddr base, + struct omap_mpu_state_s *mpu) +{ + memory_region_init_io(&mpu->tcmi_iomem, NULL, &omap_tcmi_ops, mpu, + "omap-tcmi", 0x100); + memory_region_add_subregion(memory, base, &mpu->tcmi_iomem); + omap_tcmi_reset(mpu); +} + +/* Digital phase-locked loops control */ +struct dpll_ctl_s { + MemoryRegion iomem; + uint16_t mode; + omap_clk dpll; +}; + +static uint64_t omap_dpll_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct dpll_ctl_s *s = (struct dpll_ctl_s *) opaque; + + if (size != 2) { + return omap_badwidth_read16(opaque, addr); + } + + if (addr == 0x00) /* CTL_REG */ + return s->mode; + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_dpll_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct dpll_ctl_s *s = (struct dpll_ctl_s *) opaque; + uint16_t diff; + static const int bypass_div[4] = { 1, 2, 4, 4 }; + int div, mult; + + if (size != 2) { + omap_badwidth_write16(opaque, addr, value); + return; + } + + if (addr == 0x00) { /* CTL_REG */ + /* See omap_ulpd_pm_write() too */ + diff = s->mode & value; + s->mode = value & 0x2fff; + if (diff & (0x3ff << 2)) { + if (value & (1 << 4)) { /* PLL_ENABLE */ + div = ((value >> 5) & 3) + 1; /* PLL_DIV */ + mult = MIN((value >> 7) & 0x1f, 1); /* PLL_MULT */ + } else { + div = bypass_div[((value >> 2) & 3)]; /* BYPASS_DIV */ + mult = 1; + } + omap_clk_setrate(s->dpll, div, mult); + } + + /* Enter the desired mode. */ + s->mode = (s->mode & 0xfffe) | ((s->mode >> 4) & 1); + + /* Act as if the lock is restored. */ + s->mode |= 2; + } else { + OMAP_BAD_REG(addr); + } +} + +static const MemoryRegionOps omap_dpll_ops = { + .read = omap_dpll_read, + .write = omap_dpll_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_dpll_reset(struct dpll_ctl_s *s) +{ + s->mode = 0x2002; + omap_clk_setrate(s->dpll, 1, 1); +} + +static struct dpll_ctl_s *omap_dpll_init(MemoryRegion *memory, + hwaddr base, omap_clk clk) +{ + struct dpll_ctl_s *s = g_malloc0(sizeof(*s)); + memory_region_init_io(&s->iomem, NULL, &omap_dpll_ops, s, "omap-dpll", 0x100); + + s->dpll = clk; + omap_dpll_reset(s); + + memory_region_add_subregion(memory, base, &s->iomem); + return s; +} + +/* MPU Clock/Reset/Power Mode Control */ +static uint64_t omap_clkm_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + + if (size != 2) { + return omap_badwidth_read16(opaque, addr); + } + + switch (addr) { + case 0x00: /* ARM_CKCTL */ + return s->clkm.arm_ckctl; + + case 0x04: /* ARM_IDLECT1 */ + return s->clkm.arm_idlect1; + + case 0x08: /* ARM_IDLECT2 */ + return s->clkm.arm_idlect2; + + case 0x0c: /* ARM_EWUPCT */ + return s->clkm.arm_ewupct; + + case 0x10: /* ARM_RSTCT1 */ + return s->clkm.arm_rstct1; + + case 0x14: /* ARM_RSTCT2 */ + return s->clkm.arm_rstct2; + + case 0x18: /* ARM_SYSST */ + return (s->clkm.clocking_scheme << 11) | s->clkm.cold_start; + + case 0x1c: /* ARM_CKOUT1 */ + return s->clkm.arm_ckout1; + + case 0x20: /* ARM_CKOUT2 */ + break; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static inline void omap_clkm_ckctl_update(struct omap_mpu_state_s *s, + uint16_t diff, uint16_t value) +{ + omap_clk clk; + + if (diff & (1 << 14)) { /* ARM_INTHCK_SEL */ + if (value & (1 << 14)) + /* Reserved */; + else { + clk = omap_findclk(s, "arminth_ck"); + omap_clk_reparent(clk, omap_findclk(s, "tc_ck")); + } + } + if (diff & (1 << 12)) { /* ARM_TIMXO */ + clk = omap_findclk(s, "armtim_ck"); + if (value & (1 << 12)) + omap_clk_reparent(clk, omap_findclk(s, "clkin")); + else + omap_clk_reparent(clk, omap_findclk(s, "ck_gen1")); + } + /* XXX: en_dspck */ + if (diff & (3 << 10)) { /* DSPMMUDIV */ + clk = omap_findclk(s, "dspmmu_ck"); + omap_clk_setrate(clk, 1 << ((value >> 10) & 3), 1); + } + if (diff & (3 << 8)) { /* TCDIV */ + clk = omap_findclk(s, "tc_ck"); + omap_clk_setrate(clk, 1 << ((value >> 8) & 3), 1); + } + if (diff & (3 << 6)) { /* DSPDIV */ + clk = omap_findclk(s, "dsp_ck"); + omap_clk_setrate(clk, 1 << ((value >> 6) & 3), 1); + } + if (diff & (3 << 4)) { /* ARMDIV */ + clk = omap_findclk(s, "arm_ck"); + omap_clk_setrate(clk, 1 << ((value >> 4) & 3), 1); + } + if (diff & (3 << 2)) { /* LCDDIV */ + clk = omap_findclk(s, "lcd_ck"); + omap_clk_setrate(clk, 1 << ((value >> 2) & 3), 1); + } + if (diff & (3 << 0)) { /* PERDIV */ + clk = omap_findclk(s, "armper_ck"); + omap_clk_setrate(clk, 1 << ((value >> 0) & 3), 1); + } +} + +static inline void omap_clkm_idlect1_update(struct omap_mpu_state_s *s, + uint16_t diff, uint16_t value) +{ + omap_clk clk; + + if (value & (1 << 11)) { /* SETARM_IDLE */ + cpu_interrupt(CPU(s->cpu), CPU_INTERRUPT_HALT); + } + if (!(value & (1 << 10))) /* WKUP_MODE */ + qemu_system_shutdown_request(); /* XXX: disable wakeup from IRQ */ + +#define SET_CANIDLE(clock, bit) \ + if (diff & (1 << bit)) { \ + clk = omap_findclk(s, clock); \ + omap_clk_canidle(clk, (value >> bit) & 1); \ + } + SET_CANIDLE("mpuwd_ck", 0) /* IDLWDT_ARM */ + SET_CANIDLE("armxor_ck", 1) /* IDLXORP_ARM */ + SET_CANIDLE("mpuper_ck", 2) /* IDLPER_ARM */ + SET_CANIDLE("lcd_ck", 3) /* IDLLCD_ARM */ + SET_CANIDLE("lb_ck", 4) /* IDLLB_ARM */ + SET_CANIDLE("hsab_ck", 5) /* IDLHSAB_ARM */ + SET_CANIDLE("tipb_ck", 6) /* IDLIF_ARM */ + SET_CANIDLE("dma_ck", 6) /* IDLIF_ARM */ + SET_CANIDLE("tc_ck", 6) /* IDLIF_ARM */ + SET_CANIDLE("dpll1", 7) /* IDLDPLL_ARM */ + SET_CANIDLE("dpll2", 7) /* IDLDPLL_ARM */ + SET_CANIDLE("dpll3", 7) /* IDLDPLL_ARM */ + SET_CANIDLE("mpui_ck", 8) /* IDLAPI_ARM */ + SET_CANIDLE("armtim_ck", 9) /* IDLTIM_ARM */ +} + +static inline void omap_clkm_idlect2_update(struct omap_mpu_state_s *s, + uint16_t diff, uint16_t value) +{ + omap_clk clk; + +#define SET_ONOFF(clock, bit) \ + if (diff & (1 << bit)) { \ + clk = omap_findclk(s, clock); \ + omap_clk_onoff(clk, (value >> bit) & 1); \ + } + SET_ONOFF("mpuwd_ck", 0) /* EN_WDTCK */ + SET_ONOFF("armxor_ck", 1) /* EN_XORPCK */ + SET_ONOFF("mpuper_ck", 2) /* EN_PERCK */ + SET_ONOFF("lcd_ck", 3) /* EN_LCDCK */ + SET_ONOFF("lb_ck", 4) /* EN_LBCK */ + SET_ONOFF("hsab_ck", 5) /* EN_HSABCK */ + SET_ONOFF("mpui_ck", 6) /* EN_APICK */ + SET_ONOFF("armtim_ck", 7) /* EN_TIMCK */ + SET_CANIDLE("dma_ck", 8) /* DMACK_REQ */ + SET_ONOFF("arm_gpio_ck", 9) /* EN_GPIOCK */ + SET_ONOFF("lbfree_ck", 10) /* EN_LBFREECK */ +} + +static inline void omap_clkm_ckout1_update(struct omap_mpu_state_s *s, + uint16_t diff, uint16_t value) +{ + omap_clk clk; + + if (diff & (3 << 4)) { /* TCLKOUT */ + clk = omap_findclk(s, "tclk_out"); + switch ((value >> 4) & 3) { + case 1: + omap_clk_reparent(clk, omap_findclk(s, "ck_gen3")); + omap_clk_onoff(clk, 1); + break; + case 2: + omap_clk_reparent(clk, omap_findclk(s, "tc_ck")); + omap_clk_onoff(clk, 1); + break; + default: + omap_clk_onoff(clk, 0); + } + } + if (diff & (3 << 2)) { /* DCLKOUT */ + clk = omap_findclk(s, "dclk_out"); + switch ((value >> 2) & 3) { + case 0: + omap_clk_reparent(clk, omap_findclk(s, "dspmmu_ck")); + break; + case 1: + omap_clk_reparent(clk, omap_findclk(s, "ck_gen2")); + break; + case 2: + omap_clk_reparent(clk, omap_findclk(s, "dsp_ck")); + break; + case 3: + omap_clk_reparent(clk, omap_findclk(s, "ck_ref14")); + break; + } + } + if (diff & (3 << 0)) { /* ACLKOUT */ + clk = omap_findclk(s, "aclk_out"); + switch ((value >> 0) & 3) { + case 1: + omap_clk_reparent(clk, omap_findclk(s, "ck_gen1")); + omap_clk_onoff(clk, 1); + break; + case 2: + omap_clk_reparent(clk, omap_findclk(s, "arm_ck")); + omap_clk_onoff(clk, 1); + break; + case 3: + omap_clk_reparent(clk, omap_findclk(s, "ck_ref14")); + omap_clk_onoff(clk, 1); + break; + default: + omap_clk_onoff(clk, 0); + } + } +} + +static void omap_clkm_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + uint16_t diff; + omap_clk clk; + static const char *clkschemename[8] = { + "fully synchronous", "fully asynchronous", "synchronous scalable", + "mix mode 1", "mix mode 2", "bypass mode", "mix mode 3", "mix mode 4", + }; + + if (size != 2) { + omap_badwidth_write16(opaque, addr, value); + return; + } + + switch (addr) { + case 0x00: /* ARM_CKCTL */ + diff = s->clkm.arm_ckctl ^ value; + s->clkm.arm_ckctl = value & 0x7fff; + omap_clkm_ckctl_update(s, diff, value); + return; + + case 0x04: /* ARM_IDLECT1 */ + diff = s->clkm.arm_idlect1 ^ value; + s->clkm.arm_idlect1 = value & 0x0fff; + omap_clkm_idlect1_update(s, diff, value); + return; + + case 0x08: /* ARM_IDLECT2 */ + diff = s->clkm.arm_idlect2 ^ value; + s->clkm.arm_idlect2 = value & 0x07ff; + omap_clkm_idlect2_update(s, diff, value); + return; + + case 0x0c: /* ARM_EWUPCT */ + s->clkm.arm_ewupct = value & 0x003f; + return; + + case 0x10: /* ARM_RSTCT1 */ + diff = s->clkm.arm_rstct1 ^ value; + s->clkm.arm_rstct1 = value & 0x0007; + if (value & 9) { + qemu_system_reset_request(); + s->clkm.cold_start = 0xa; + } + if (diff & ~value & 4) { /* DSP_RST */ + omap_mpui_reset(s); + omap_tipb_bridge_reset(s->private_tipb); + omap_tipb_bridge_reset(s->public_tipb); + } + if (diff & 2) { /* DSP_EN */ + clk = omap_findclk(s, "dsp_ck"); + omap_clk_canidle(clk, (~value >> 1) & 1); + } + return; + + case 0x14: /* ARM_RSTCT2 */ + s->clkm.arm_rstct2 = value & 0x0001; + return; + + case 0x18: /* ARM_SYSST */ + if ((s->clkm.clocking_scheme ^ (value >> 11)) & 7) { + s->clkm.clocking_scheme = (value >> 11) & 7; + printf("%s: clocking scheme set to %s\n", __FUNCTION__, + clkschemename[s->clkm.clocking_scheme]); + } + s->clkm.cold_start &= value & 0x3f; + return; + + case 0x1c: /* ARM_CKOUT1 */ + diff = s->clkm.arm_ckout1 ^ value; + s->clkm.arm_ckout1 = value & 0x003f; + omap_clkm_ckout1_update(s, diff, value); + return; + + case 0x20: /* ARM_CKOUT2 */ + default: + OMAP_BAD_REG(addr); + } +} + +static const MemoryRegionOps omap_clkm_ops = { + .read = omap_clkm_read, + .write = omap_clkm_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static uint64_t omap_clkdsp_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + CPUState *cpu = CPU(s->cpu); + + if (size != 2) { + return omap_badwidth_read16(opaque, addr); + } + + switch (addr) { + case 0x04: /* DSP_IDLECT1 */ + return s->clkm.dsp_idlect1; + + case 0x08: /* DSP_IDLECT2 */ + return s->clkm.dsp_idlect2; + + case 0x14: /* DSP_RSTCT2 */ + return s->clkm.dsp_rstct2; + + case 0x18: /* DSP_SYSST */ + cpu = CPU(s->cpu); + return (s->clkm.clocking_scheme << 11) | s->clkm.cold_start | + (cpu->halted << 6); /* Quite useless... */ + } + + OMAP_BAD_REG(addr); + return 0; +} + +static inline void omap_clkdsp_idlect1_update(struct omap_mpu_state_s *s, + uint16_t diff, uint16_t value) +{ + omap_clk clk; + + SET_CANIDLE("dspxor_ck", 1); /* IDLXORP_DSP */ +} + +static inline void omap_clkdsp_idlect2_update(struct omap_mpu_state_s *s, + uint16_t diff, uint16_t value) +{ + omap_clk clk; + + SET_ONOFF("dspxor_ck", 1); /* EN_XORPCK */ +} + +static void omap_clkdsp_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + uint16_t diff; + + if (size != 2) { + omap_badwidth_write16(opaque, addr, value); + return; + } + + switch (addr) { + case 0x04: /* DSP_IDLECT1 */ + diff = s->clkm.dsp_idlect1 ^ value; + s->clkm.dsp_idlect1 = value & 0x01f7; + omap_clkdsp_idlect1_update(s, diff, value); + break; + + case 0x08: /* DSP_IDLECT2 */ + s->clkm.dsp_idlect2 = value & 0x0037; + diff = s->clkm.dsp_idlect1 ^ value; + omap_clkdsp_idlect2_update(s, diff, value); + break; + + case 0x14: /* DSP_RSTCT2 */ + s->clkm.dsp_rstct2 = value & 0x0001; + break; + + case 0x18: /* DSP_SYSST */ + s->clkm.cold_start &= value & 0x3f; + break; + + default: + OMAP_BAD_REG(addr); + } +} + +static const MemoryRegionOps omap_clkdsp_ops = { + .read = omap_clkdsp_read, + .write = omap_clkdsp_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_clkm_reset(struct omap_mpu_state_s *s) +{ + if (s->wdt && s->wdt->reset) + s->clkm.cold_start = 0x6; + s->clkm.clocking_scheme = 0; + omap_clkm_ckctl_update(s, ~0, 0x3000); + s->clkm.arm_ckctl = 0x3000; + omap_clkm_idlect1_update(s, s->clkm.arm_idlect1 ^ 0x0400, 0x0400); + s->clkm.arm_idlect1 = 0x0400; + omap_clkm_idlect2_update(s, s->clkm.arm_idlect2 ^ 0x0100, 0x0100); + s->clkm.arm_idlect2 = 0x0100; + s->clkm.arm_ewupct = 0x003f; + s->clkm.arm_rstct1 = 0x0000; + s->clkm.arm_rstct2 = 0x0000; + s->clkm.arm_ckout1 = 0x0015; + s->clkm.dpll1_mode = 0x2002; + omap_clkdsp_idlect1_update(s, s->clkm.dsp_idlect1 ^ 0x0040, 0x0040); + s->clkm.dsp_idlect1 = 0x0040; + omap_clkdsp_idlect2_update(s, ~0, 0x0000); + s->clkm.dsp_idlect2 = 0x0000; + s->clkm.dsp_rstct2 = 0x0000; +} + +static void omap_clkm_init(MemoryRegion *memory, hwaddr mpu_base, + hwaddr dsp_base, struct omap_mpu_state_s *s) +{ + memory_region_init_io(&s->clkm_iomem, NULL, &omap_clkm_ops, s, + "omap-clkm", 0x100); + memory_region_init_io(&s->clkdsp_iomem, NULL, &omap_clkdsp_ops, s, + "omap-clkdsp", 0x1000); + + s->clkm.arm_idlect1 = 0x03ff; + s->clkm.arm_idlect2 = 0x0100; + s->clkm.dsp_idlect1 = 0x0002; + omap_clkm_reset(s); + s->clkm.cold_start = 0x3a; + + memory_region_add_subregion(memory, mpu_base, &s->clkm_iomem); + memory_region_add_subregion(memory, dsp_base, &s->clkdsp_iomem); +} + +/* MPU I/O */ +struct omap_mpuio_s { + qemu_irq irq; + qemu_irq kbd_irq; + qemu_irq *in; + qemu_irq handler[16]; + qemu_irq wakeup; + MemoryRegion iomem; + + uint16_t inputs; + uint16_t outputs; + uint16_t dir; + uint16_t edge; + uint16_t mask; + uint16_t ints; + + uint16_t debounce; + uint16_t latch; + uint8_t event; + + uint8_t buttons[5]; + uint8_t row_latch; + uint8_t cols; + int kbd_mask; + int clk; +}; + +static void omap_mpuio_set(void *opaque, int line, int level) +{ + struct omap_mpuio_s *s = (struct omap_mpuio_s *) opaque; + uint16_t prev = s->inputs; + + if (level) + s->inputs |= 1 << line; + else + s->inputs &= ~(1 << line); + + if (((1 << line) & s->dir & ~s->mask) && s->clk) { + if ((s->edge & s->inputs & ~prev) | (~s->edge & ~s->inputs & prev)) { + s->ints |= 1 << line; + qemu_irq_raise(s->irq); + /* TODO: wakeup */ + } + if ((s->event & (1 << 0)) && /* SET_GPIO_EVENT_MODE */ + (s->event >> 1) == line) /* PIN_SELECT */ + s->latch = s->inputs; + } +} + +static void omap_mpuio_kbd_update(struct omap_mpuio_s *s) +{ + int i; + uint8_t *row, rows = 0, cols = ~s->cols; + + for (row = s->buttons + 4, i = 1 << 4; i; row --, i >>= 1) + if (*row & cols) + rows |= i; + + qemu_set_irq(s->kbd_irq, rows && !s->kbd_mask && s->clk); + s->row_latch = ~rows; +} + +static uint64_t omap_mpuio_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_mpuio_s *s = (struct omap_mpuio_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + uint16_t ret; + + if (size != 2) { + return omap_badwidth_read16(opaque, addr); + } + + switch (offset) { + case 0x00: /* INPUT_LATCH */ + return s->inputs; + + case 0x04: /* OUTPUT_REG */ + return s->outputs; + + case 0x08: /* IO_CNTL */ + return s->dir; + + case 0x10: /* KBR_LATCH */ + return s->row_latch; + + case 0x14: /* KBC_REG */ + return s->cols; + + case 0x18: /* GPIO_EVENT_MODE_REG */ + return s->event; + + case 0x1c: /* GPIO_INT_EDGE_REG */ + return s->edge; + + case 0x20: /* KBD_INT */ + return (~s->row_latch & 0x1f) && !s->kbd_mask; + + case 0x24: /* GPIO_INT */ + ret = s->ints; + s->ints &= s->mask; + if (ret) + qemu_irq_lower(s->irq); + return ret; + + case 0x28: /* KBD_MASKIT */ + return s->kbd_mask; + + case 0x2c: /* GPIO_MASKIT */ + return s->mask; + + case 0x30: /* GPIO_DEBOUNCING_REG */ + return s->debounce; + + case 0x34: /* GPIO_LATCH_REG */ + return s->latch; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_mpuio_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_mpuio_s *s = (struct omap_mpuio_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + uint16_t diff; + int ln; + + if (size != 2) { + omap_badwidth_write16(opaque, addr, value); + return; + } + + switch (offset) { + case 0x04: /* OUTPUT_REG */ + diff = (s->outputs ^ value) & ~s->dir; + s->outputs = value; + while ((ln = ctz32(diff)) != 32) { + if (s->handler[ln]) + qemu_set_irq(s->handler[ln], (value >> ln) & 1); + diff &= ~(1 << ln); + } + break; + + case 0x08: /* IO_CNTL */ + diff = s->outputs & (s->dir ^ value); + s->dir = value; + + value = s->outputs & ~s->dir; + while ((ln = ctz32(diff)) != 32) { + if (s->handler[ln]) + qemu_set_irq(s->handler[ln], (value >> ln) & 1); + diff &= ~(1 << ln); + } + break; + + case 0x14: /* KBC_REG */ + s->cols = value; + omap_mpuio_kbd_update(s); + break; + + case 0x18: /* GPIO_EVENT_MODE_REG */ + s->event = value & 0x1f; + break; + + case 0x1c: /* GPIO_INT_EDGE_REG */ + s->edge = value; + break; + + case 0x28: /* KBD_MASKIT */ + s->kbd_mask = value & 1; + omap_mpuio_kbd_update(s); + break; + + case 0x2c: /* GPIO_MASKIT */ + s->mask = value; + break; + + case 0x30: /* GPIO_DEBOUNCING_REG */ + s->debounce = value & 0x1ff; + break; + + case 0x00: /* INPUT_LATCH */ + case 0x10: /* KBR_LATCH */ + case 0x20: /* KBD_INT */ + case 0x24: /* GPIO_INT */ + case 0x34: /* GPIO_LATCH_REG */ + OMAP_RO_REG(addr); + return; + + default: + OMAP_BAD_REG(addr); + return; + } +} + +static const MemoryRegionOps omap_mpuio_ops = { + .read = omap_mpuio_read, + .write = omap_mpuio_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_mpuio_reset(struct omap_mpuio_s *s) +{ + s->inputs = 0; + s->outputs = 0; + s->dir = ~0; + s->event = 0; + s->edge = 0; + s->kbd_mask = 0; + s->mask = 0; + s->debounce = 0; + s->latch = 0; + s->ints = 0; + s->row_latch = 0x1f; + s->clk = 1; +} + +static void omap_mpuio_onoff(void *opaque, int line, int on) +{ + struct omap_mpuio_s *s = (struct omap_mpuio_s *) opaque; + + s->clk = on; + if (on) + omap_mpuio_kbd_update(s); +} + +static struct omap_mpuio_s *omap_mpuio_init(MemoryRegion *memory, + hwaddr base, + qemu_irq kbd_int, qemu_irq gpio_int, qemu_irq wakeup, + omap_clk clk) +{ + struct omap_mpuio_s *s = (struct omap_mpuio_s *) + g_malloc0(sizeof(struct omap_mpuio_s)); + + s->irq = gpio_int; + s->kbd_irq = kbd_int; + s->wakeup = wakeup; + s->in = qemu_allocate_irqs(omap_mpuio_set, s, 16); + omap_mpuio_reset(s); + + memory_region_init_io(&s->iomem, NULL, &omap_mpuio_ops, s, + "omap-mpuio", 0x800); + memory_region_add_subregion(memory, base, &s->iomem); + + omap_clk_adduser(clk, qemu_allocate_irq(omap_mpuio_onoff, s, 0)); + + return s; +} + +qemu_irq *omap_mpuio_in_get(struct omap_mpuio_s *s) +{ + return s->in; +} + +void omap_mpuio_out_set(struct omap_mpuio_s *s, int line, qemu_irq handler) +{ + if (line >= 16 || line < 0) + hw_error("%s: No GPIO line %i\n", __FUNCTION__, line); + s->handler[line] = handler; +} + +void omap_mpuio_key(struct omap_mpuio_s *s, int row, int col, int down) +{ + if (row >= 5 || row < 0) + hw_error("%s: No key %i-%i\n", __FUNCTION__, col, row); + + if (down) + s->buttons[row] |= 1 << col; + else + s->buttons[row] &= ~(1 << col); + + omap_mpuio_kbd_update(s); +} + +/* MicroWire Interface */ +struct omap_uwire_s { + MemoryRegion iomem; + qemu_irq txirq; + qemu_irq rxirq; + qemu_irq txdrq; + + uint16_t txbuf; + uint16_t rxbuf; + uint16_t control; + uint16_t setup[5]; + + uWireSlave *chip[4]; +}; + +static void omap_uwire_transfer_start(struct omap_uwire_s *s) +{ + int chipselect = (s->control >> 10) & 3; /* INDEX */ + uWireSlave *slave = s->chip[chipselect]; + + if ((s->control >> 5) & 0x1f) { /* NB_BITS_WR */ + if (s->control & (1 << 12)) /* CS_CMD */ + if (slave && slave->send) + slave->send(slave->opaque, + s->txbuf >> (16 - ((s->control >> 5) & 0x1f))); + s->control &= ~(1 << 14); /* CSRB */ + /* TODO: depending on s->setup[4] bits [1:0] assert an IRQ or + * a DRQ. When is the level IRQ supposed to be reset? */ + } + + if ((s->control >> 0) & 0x1f) { /* NB_BITS_RD */ + if (s->control & (1 << 12)) /* CS_CMD */ + if (slave && slave->receive) + s->rxbuf = slave->receive(slave->opaque); + s->control |= 1 << 15; /* RDRB */ + /* TODO: depending on s->setup[4] bits [1:0] assert an IRQ or + * a DRQ. When is the level IRQ supposed to be reset? */ + } +} + +static uint64_t omap_uwire_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_uwire_s *s = (struct omap_uwire_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + if (size != 2) { + return omap_badwidth_read16(opaque, addr); + } + + switch (offset) { + case 0x00: /* RDR */ + s->control &= ~(1 << 15); /* RDRB */ + return s->rxbuf; + + case 0x04: /* CSR */ + return s->control; + + case 0x08: /* SR1 */ + return s->setup[0]; + case 0x0c: /* SR2 */ + return s->setup[1]; + case 0x10: /* SR3 */ + return s->setup[2]; + case 0x14: /* SR4 */ + return s->setup[3]; + case 0x18: /* SR5 */ + return s->setup[4]; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_uwire_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_uwire_s *s = (struct omap_uwire_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + if (size != 2) { + omap_badwidth_write16(opaque, addr, value); + return; + } + + switch (offset) { + case 0x00: /* TDR */ + s->txbuf = value; /* TD */ + if ((s->setup[4] & (1 << 2)) && /* AUTO_TX_EN */ + ((s->setup[4] & (1 << 3)) || /* CS_TOGGLE_TX_EN */ + (s->control & (1 << 12)))) { /* CS_CMD */ + s->control |= 1 << 14; /* CSRB */ + omap_uwire_transfer_start(s); + } + break; + + case 0x04: /* CSR */ + s->control = value & 0x1fff; + if (value & (1 << 13)) /* START */ + omap_uwire_transfer_start(s); + break; + + case 0x08: /* SR1 */ + s->setup[0] = value & 0x003f; + break; + + case 0x0c: /* SR2 */ + s->setup[1] = value & 0x0fc0; + break; + + case 0x10: /* SR3 */ + s->setup[2] = value & 0x0003; + break; + + case 0x14: /* SR4 */ + s->setup[3] = value & 0x0001; + break; + + case 0x18: /* SR5 */ + s->setup[4] = value & 0x000f; + break; + + default: + OMAP_BAD_REG(addr); + return; + } +} + +static const MemoryRegionOps omap_uwire_ops = { + .read = omap_uwire_read, + .write = omap_uwire_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_uwire_reset(struct omap_uwire_s *s) +{ + s->control = 0; + s->setup[0] = 0; + s->setup[1] = 0; + s->setup[2] = 0; + s->setup[3] = 0; + s->setup[4] = 0; +} + +static struct omap_uwire_s *omap_uwire_init(MemoryRegion *system_memory, + hwaddr base, + qemu_irq txirq, qemu_irq rxirq, + qemu_irq dma, + omap_clk clk) +{ + struct omap_uwire_s *s = (struct omap_uwire_s *) + g_malloc0(sizeof(struct omap_uwire_s)); + + s->txirq = txirq; + s->rxirq = rxirq; + s->txdrq = dma; + omap_uwire_reset(s); + + memory_region_init_io(&s->iomem, NULL, &omap_uwire_ops, s, "omap-uwire", 0x800); + memory_region_add_subregion(system_memory, base, &s->iomem); + + return s; +} + +void omap_uwire_attach(struct omap_uwire_s *s, + uWireSlave *slave, int chipselect) +{ + if (chipselect < 0 || chipselect > 3) { + fprintf(stderr, "%s: Bad chipselect %i\n", __FUNCTION__, chipselect); + exit(-1); + } + + s->chip[chipselect] = slave; +} + +/* Pseudonoise Pulse-Width Light Modulator */ +struct omap_pwl_s { + MemoryRegion iomem; + uint8_t output; + uint8_t level; + uint8_t enable; + int clk; +}; + +static void omap_pwl_update(struct omap_pwl_s *s) +{ + int output = (s->clk && s->enable) ? s->level : 0; + + if (output != s->output) { + s->output = output; + printf("%s: Backlight now at %i/256\n", __FUNCTION__, output); + } +} + +static uint64_t omap_pwl_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_pwl_s *s = (struct omap_pwl_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + if (size != 1) { + return omap_badwidth_read8(opaque, addr); + } + + switch (offset) { + case 0x00: /* PWL_LEVEL */ + return s->level; + case 0x04: /* PWL_CTRL */ + return s->enable; + } + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_pwl_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_pwl_s *s = (struct omap_pwl_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + if (size != 1) { + omap_badwidth_write8(opaque, addr, value); + return; + } + + switch (offset) { + case 0x00: /* PWL_LEVEL */ + s->level = value; + omap_pwl_update(s); + break; + case 0x04: /* PWL_CTRL */ + s->enable = value & 1; + omap_pwl_update(s); + break; + default: + OMAP_BAD_REG(addr); + return; + } +} + +static const MemoryRegionOps omap_pwl_ops = { + .read = omap_pwl_read, + .write = omap_pwl_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_pwl_reset(struct omap_pwl_s *s) +{ + s->output = 0; + s->level = 0; + s->enable = 0; + s->clk = 1; + omap_pwl_update(s); +} + +static void omap_pwl_clk_update(void *opaque, int line, int on) +{ + struct omap_pwl_s *s = (struct omap_pwl_s *) opaque; + + s->clk = on; + omap_pwl_update(s); +} + +static struct omap_pwl_s *omap_pwl_init(MemoryRegion *system_memory, + hwaddr base, + omap_clk clk) +{ + struct omap_pwl_s *s = g_malloc0(sizeof(*s)); + + omap_pwl_reset(s); + + memory_region_init_io(&s->iomem, NULL, &omap_pwl_ops, s, + "omap-pwl", 0x800); + memory_region_add_subregion(system_memory, base, &s->iomem); + + omap_clk_adduser(clk, qemu_allocate_irq(omap_pwl_clk_update, s, 0)); + return s; +} + +/* Pulse-Width Tone module */ +struct omap_pwt_s { + MemoryRegion iomem; + uint8_t frc; + uint8_t vrc; + uint8_t gcr; + omap_clk clk; +}; + +static uint64_t omap_pwt_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_pwt_s *s = (struct omap_pwt_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + if (size != 1) { + return omap_badwidth_read8(opaque, addr); + } + + switch (offset) { + case 0x00: /* FRC */ + return s->frc; + case 0x04: /* VCR */ + return s->vrc; + case 0x08: /* GCR */ + return s->gcr; + } + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_pwt_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_pwt_s *s = (struct omap_pwt_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + if (size != 1) { + omap_badwidth_write8(opaque, addr, value); + return; + } + + switch (offset) { + case 0x00: /* FRC */ + s->frc = value & 0x3f; + break; + case 0x04: /* VRC */ + if ((value ^ s->vrc) & 1) { + if (value & 1) + printf("%s: %iHz buzz on\n", __FUNCTION__, (int) + /* 1.5 MHz from a 12-MHz or 13-MHz PWT_CLK */ + ((omap_clk_getrate(s->clk) >> 3) / + /* Pre-multiplexer divider */ + ((s->gcr & 2) ? 1 : 154) / + /* Octave multiplexer */ + (2 << (value & 3)) * + /* 101/107 divider */ + ((value & (1 << 2)) ? 101 : 107) * + /* 49/55 divider */ + ((value & (1 << 3)) ? 49 : 55) * + /* 50/63 divider */ + ((value & (1 << 4)) ? 50 : 63) * + /* 80/127 divider */ + ((value & (1 << 5)) ? 80 : 127) / + (107 * 55 * 63 * 127))); + else + printf("%s: silence!\n", __FUNCTION__); + } + s->vrc = value & 0x7f; + break; + case 0x08: /* GCR */ + s->gcr = value & 3; + break; + default: + OMAP_BAD_REG(addr); + return; + } +} + +static const MemoryRegionOps omap_pwt_ops = { + .read =omap_pwt_read, + .write = omap_pwt_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_pwt_reset(struct omap_pwt_s *s) +{ + s->frc = 0; + s->vrc = 0; + s->gcr = 0; +} + +static struct omap_pwt_s *omap_pwt_init(MemoryRegion *system_memory, + hwaddr base, + omap_clk clk) +{ + struct omap_pwt_s *s = g_malloc0(sizeof(*s)); + s->clk = clk; + omap_pwt_reset(s); + + memory_region_init_io(&s->iomem, NULL, &omap_pwt_ops, s, + "omap-pwt", 0x800); + memory_region_add_subregion(system_memory, base, &s->iomem); + return s; +} + +/* Real-time Clock module */ +struct omap_rtc_s { + MemoryRegion iomem; + qemu_irq irq; + qemu_irq alarm; + QEMUTimer *clk; + + uint8_t interrupts; + uint8_t status; + int16_t comp_reg; + int running; + int pm_am; + int auto_comp; + int round; + struct tm alarm_tm; + time_t alarm_ti; + + struct tm current_tm; + time_t ti; + uint64_t tick; +}; + +static void omap_rtc_interrupts_update(struct omap_rtc_s *s) +{ + /* s->alarm is level-triggered */ + qemu_set_irq(s->alarm, (s->status >> 6) & 1); +} + +static void omap_rtc_alarm_update(struct omap_rtc_s *s) +{ + s->alarm_ti = mktimegm(&s->alarm_tm); + if (s->alarm_ti == -1) + printf("%s: conversion failed\n", __FUNCTION__); +} + +static uint64_t omap_rtc_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_rtc_s *s = (struct omap_rtc_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + uint8_t i; + + if (size != 1) { + return omap_badwidth_read8(opaque, addr); + } + + switch (offset) { + case 0x00: /* SECONDS_REG */ + return to_bcd(s->current_tm.tm_sec); + + case 0x04: /* MINUTES_REG */ + return to_bcd(s->current_tm.tm_min); + + case 0x08: /* HOURS_REG */ + if (s->pm_am) + return ((s->current_tm.tm_hour > 11) << 7) | + to_bcd(((s->current_tm.tm_hour - 1) % 12) + 1); + else + return to_bcd(s->current_tm.tm_hour); + + case 0x0c: /* DAYS_REG */ + return to_bcd(s->current_tm.tm_mday); + + case 0x10: /* MONTHS_REG */ + return to_bcd(s->current_tm.tm_mon + 1); + + case 0x14: /* YEARS_REG */ + return to_bcd(s->current_tm.tm_year % 100); + + case 0x18: /* WEEK_REG */ + return s->current_tm.tm_wday; + + case 0x20: /* ALARM_SECONDS_REG */ + return to_bcd(s->alarm_tm.tm_sec); + + case 0x24: /* ALARM_MINUTES_REG */ + return to_bcd(s->alarm_tm.tm_min); + + case 0x28: /* ALARM_HOURS_REG */ + if (s->pm_am) + return ((s->alarm_tm.tm_hour > 11) << 7) | + to_bcd(((s->alarm_tm.tm_hour - 1) % 12) + 1); + else + return to_bcd(s->alarm_tm.tm_hour); + + case 0x2c: /* ALARM_DAYS_REG */ + return to_bcd(s->alarm_tm.tm_mday); + + case 0x30: /* ALARM_MONTHS_REG */ + return to_bcd(s->alarm_tm.tm_mon + 1); + + case 0x34: /* ALARM_YEARS_REG */ + return to_bcd(s->alarm_tm.tm_year % 100); + + case 0x40: /* RTC_CTRL_REG */ + return (s->pm_am << 3) | (s->auto_comp << 2) | + (s->round << 1) | s->running; + + case 0x44: /* RTC_STATUS_REG */ + i = s->status; + s->status &= ~0x3d; + return i; + + case 0x48: /* RTC_INTERRUPTS_REG */ + return s->interrupts; + + case 0x4c: /* RTC_COMP_LSB_REG */ + return ((uint16_t) s->comp_reg) & 0xff; + + case 0x50: /* RTC_COMP_MSB_REG */ + return ((uint16_t) s->comp_reg) >> 8; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_rtc_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_rtc_s *s = (struct omap_rtc_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + struct tm new_tm; + time_t ti[2]; + + if (size != 1) { + omap_badwidth_write8(opaque, addr, value); + return; + } + + switch (offset) { + case 0x00: /* SECONDS_REG */ +#ifdef ALMDEBUG + printf("RTC SEC_REG <-- %02x\n", value); +#endif + s->ti -= s->current_tm.tm_sec; + s->ti += from_bcd(value); + return; + + case 0x04: /* MINUTES_REG */ +#ifdef ALMDEBUG + printf("RTC MIN_REG <-- %02x\n", value); +#endif + s->ti -= s->current_tm.tm_min * 60; + s->ti += from_bcd(value) * 60; + return; + + case 0x08: /* HOURS_REG */ +#ifdef ALMDEBUG + printf("RTC HRS_REG <-- %02x\n", value); +#endif + s->ti -= s->current_tm.tm_hour * 3600; + if (s->pm_am) { + s->ti += (from_bcd(value & 0x3f) & 12) * 3600; + s->ti += ((value >> 7) & 1) * 43200; + } else + s->ti += from_bcd(value & 0x3f) * 3600; + return; + + case 0x0c: /* DAYS_REG */ +#ifdef ALMDEBUG + printf("RTC DAY_REG <-- %02x\n", value); +#endif + s->ti -= s->current_tm.tm_mday * 86400; + s->ti += from_bcd(value) * 86400; + return; + + case 0x10: /* MONTHS_REG */ +#ifdef ALMDEBUG + printf("RTC MTH_REG <-- %02x\n", value); +#endif + memcpy(&new_tm, &s->current_tm, sizeof(new_tm)); + new_tm.tm_mon = from_bcd(value); + ti[0] = mktimegm(&s->current_tm); + ti[1] = mktimegm(&new_tm); + + if (ti[0] != -1 && ti[1] != -1) { + s->ti -= ti[0]; + s->ti += ti[1]; + } else { + /* A less accurate version */ + s->ti -= s->current_tm.tm_mon * 2592000; + s->ti += from_bcd(value) * 2592000; + } + return; + + case 0x14: /* YEARS_REG */ +#ifdef ALMDEBUG + printf("RTC YRS_REG <-- %02x\n", value); +#endif + memcpy(&new_tm, &s->current_tm, sizeof(new_tm)); + new_tm.tm_year += from_bcd(value) - (new_tm.tm_year % 100); + ti[0] = mktimegm(&s->current_tm); + ti[1] = mktimegm(&new_tm); + + if (ti[0] != -1 && ti[1] != -1) { + s->ti -= ti[0]; + s->ti += ti[1]; + } else { + /* A less accurate version */ + s->ti -= (time_t)(s->current_tm.tm_year % 100) * 31536000; + s->ti += (time_t)from_bcd(value) * 31536000; + } + return; + + case 0x18: /* WEEK_REG */ + return; /* Ignored */ + + case 0x20: /* ALARM_SECONDS_REG */ +#ifdef ALMDEBUG + printf("ALM SEC_REG <-- %02x\n", value); +#endif + s->alarm_tm.tm_sec = from_bcd(value); + omap_rtc_alarm_update(s); + return; + + case 0x24: /* ALARM_MINUTES_REG */ +#ifdef ALMDEBUG + printf("ALM MIN_REG <-- %02x\n", value); +#endif + s->alarm_tm.tm_min = from_bcd(value); + omap_rtc_alarm_update(s); + return; + + case 0x28: /* ALARM_HOURS_REG */ +#ifdef ALMDEBUG + printf("ALM HRS_REG <-- %02x\n", value); +#endif + if (s->pm_am) + s->alarm_tm.tm_hour = + ((from_bcd(value & 0x3f)) % 12) + + ((value >> 7) & 1) * 12; + else + s->alarm_tm.tm_hour = from_bcd(value); + omap_rtc_alarm_update(s); + return; + + case 0x2c: /* ALARM_DAYS_REG */ +#ifdef ALMDEBUG + printf("ALM DAY_REG <-- %02x\n", value); +#endif + s->alarm_tm.tm_mday = from_bcd(value); + omap_rtc_alarm_update(s); + return; + + case 0x30: /* ALARM_MONTHS_REG */ +#ifdef ALMDEBUG + printf("ALM MON_REG <-- %02x\n", value); +#endif + s->alarm_tm.tm_mon = from_bcd(value); + omap_rtc_alarm_update(s); + return; + + case 0x34: /* ALARM_YEARS_REG */ +#ifdef ALMDEBUG + printf("ALM YRS_REG <-- %02x\n", value); +#endif + s->alarm_tm.tm_year = from_bcd(value); + omap_rtc_alarm_update(s); + return; + + case 0x40: /* RTC_CTRL_REG */ +#ifdef ALMDEBUG + printf("RTC CONTROL <-- %02x\n", value); +#endif + s->pm_am = (value >> 3) & 1; + s->auto_comp = (value >> 2) & 1; + s->round = (value >> 1) & 1; + s->running = value & 1; + s->status &= 0xfd; + s->status |= s->running << 1; + return; + + case 0x44: /* RTC_STATUS_REG */ +#ifdef ALMDEBUG + printf("RTC STATUSL <-- %02x\n", value); +#endif + s->status &= ~((value & 0xc0) ^ 0x80); + omap_rtc_interrupts_update(s); + return; + + case 0x48: /* RTC_INTERRUPTS_REG */ +#ifdef ALMDEBUG + printf("RTC INTRS <-- %02x\n", value); +#endif + s->interrupts = value; + return; + + case 0x4c: /* RTC_COMP_LSB_REG */ +#ifdef ALMDEBUG + printf("RTC COMPLSB <-- %02x\n", value); +#endif + s->comp_reg &= 0xff00; + s->comp_reg |= 0x00ff & value; + return; + + case 0x50: /* RTC_COMP_MSB_REG */ +#ifdef ALMDEBUG + printf("RTC COMPMSB <-- %02x\n", value); +#endif + s->comp_reg &= 0x00ff; + s->comp_reg |= 0xff00 & (value << 8); + return; + + default: + OMAP_BAD_REG(addr); + return; + } +} + +static const MemoryRegionOps omap_rtc_ops = { + .read = omap_rtc_read, + .write = omap_rtc_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_rtc_tick(void *opaque) +{ + struct omap_rtc_s *s = opaque; + + if (s->round) { + /* Round to nearest full minute. */ + if (s->current_tm.tm_sec < 30) + s->ti -= s->current_tm.tm_sec; + else + s->ti += 60 - s->current_tm.tm_sec; + + s->round = 0; + } + + localtime_r(&s->ti, &s->current_tm); + + if ((s->interrupts & 0x08) && s->ti == s->alarm_ti) { + s->status |= 0x40; + omap_rtc_interrupts_update(s); + } + + if (s->interrupts & 0x04) + switch (s->interrupts & 3) { + case 0: + s->status |= 0x04; + qemu_irq_pulse(s->irq); + break; + case 1: + if (s->current_tm.tm_sec) + break; + s->status |= 0x08; + qemu_irq_pulse(s->irq); + break; + case 2: + if (s->current_tm.tm_sec || s->current_tm.tm_min) + break; + s->status |= 0x10; + qemu_irq_pulse(s->irq); + break; + case 3: + if (s->current_tm.tm_sec || + s->current_tm.tm_min || s->current_tm.tm_hour) + break; + s->status |= 0x20; + qemu_irq_pulse(s->irq); + break; + } + + /* Move on */ + if (s->running) + s->ti ++; + s->tick += 1000; + + /* + * Every full hour add a rough approximation of the compensation + * register to the 32kHz Timer (which drives the RTC) value. + */ + if (s->auto_comp && !s->current_tm.tm_sec && !s->current_tm.tm_min) + s->tick += s->comp_reg * 1000 / 32768; + + timer_mod(s->clk, s->tick); +} + +static void omap_rtc_reset(struct omap_rtc_s *s) +{ + struct tm tm; + + s->interrupts = 0; + s->comp_reg = 0; + s->running = 0; + s->pm_am = 0; + s->auto_comp = 0; + s->round = 0; + s->tick = qemu_clock_get_ms(rtc_clock); + memset(&s->alarm_tm, 0, sizeof(s->alarm_tm)); + s->alarm_tm.tm_mday = 0x01; + s->status = 1 << 7; + qemu_get_timedate(&tm, 0); + s->ti = mktimegm(&tm); + + omap_rtc_alarm_update(s); + omap_rtc_tick(s); +} + +static struct omap_rtc_s *omap_rtc_init(MemoryRegion *system_memory, + hwaddr base, + qemu_irq timerirq, qemu_irq alarmirq, + omap_clk clk) +{ + struct omap_rtc_s *s = (struct omap_rtc_s *) + g_malloc0(sizeof(struct omap_rtc_s)); + + s->irq = timerirq; + s->alarm = alarmirq; + s->clk = timer_new_ms(rtc_clock, omap_rtc_tick, s); + + omap_rtc_reset(s); + + memory_region_init_io(&s->iomem, NULL, &omap_rtc_ops, s, + "omap-rtc", 0x800); + memory_region_add_subregion(system_memory, base, &s->iomem); + + return s; +} + +/* Multi-channel Buffered Serial Port interfaces */ +struct omap_mcbsp_s { + MemoryRegion iomem; + qemu_irq txirq; + qemu_irq rxirq; + qemu_irq txdrq; + qemu_irq rxdrq; + + uint16_t spcr[2]; + uint16_t rcr[2]; + uint16_t xcr[2]; + uint16_t srgr[2]; + uint16_t mcr[2]; + uint16_t pcr; + uint16_t rcer[8]; + uint16_t xcer[8]; + int tx_rate; + int rx_rate; + int tx_req; + int rx_req; + + I2SCodec *codec; + QEMUTimer *source_timer; + QEMUTimer *sink_timer; +}; + +static void omap_mcbsp_intr_update(struct omap_mcbsp_s *s) +{ + int irq; + + switch ((s->spcr[0] >> 4) & 3) { /* RINTM */ + case 0: + irq = (s->spcr[0] >> 1) & 1; /* RRDY */ + break; + case 3: + irq = (s->spcr[0] >> 3) & 1; /* RSYNCERR */ + break; + default: + irq = 0; + break; + } + + if (irq) + qemu_irq_pulse(s->rxirq); + + switch ((s->spcr[1] >> 4) & 3) { /* XINTM */ + case 0: + irq = (s->spcr[1] >> 1) & 1; /* XRDY */ + break; + case 3: + irq = (s->spcr[1] >> 3) & 1; /* XSYNCERR */ + break; + default: + irq = 0; + break; + } + + if (irq) + qemu_irq_pulse(s->txirq); +} + +static void omap_mcbsp_rx_newdata(struct omap_mcbsp_s *s) +{ + if ((s->spcr[0] >> 1) & 1) /* RRDY */ + s->spcr[0] |= 1 << 2; /* RFULL */ + s->spcr[0] |= 1 << 1; /* RRDY */ + qemu_irq_raise(s->rxdrq); + omap_mcbsp_intr_update(s); +} + +static void omap_mcbsp_source_tick(void *opaque) +{ + struct omap_mcbsp_s *s = (struct omap_mcbsp_s *) opaque; + static const int bps[8] = { 0, 1, 1, 2, 2, 2, -255, -255 }; + + if (!s->rx_rate) + return; + if (s->rx_req) + printf("%s: Rx FIFO overrun\n", __FUNCTION__); + + s->rx_req = s->rx_rate << bps[(s->rcr[0] >> 5) & 7]; + + omap_mcbsp_rx_newdata(s); + timer_mod(s->source_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + get_ticks_per_sec()); +} + +static void omap_mcbsp_rx_start(struct omap_mcbsp_s *s) +{ + if (!s->codec || !s->codec->rts) + omap_mcbsp_source_tick(s); + else if (s->codec->in.len) { + s->rx_req = s->codec->in.len; + omap_mcbsp_rx_newdata(s); + } +} + +static void omap_mcbsp_rx_stop(struct omap_mcbsp_s *s) +{ + timer_del(s->source_timer); +} + +static void omap_mcbsp_rx_done(struct omap_mcbsp_s *s) +{ + s->spcr[0] &= ~(1 << 1); /* RRDY */ + qemu_irq_lower(s->rxdrq); + omap_mcbsp_intr_update(s); +} + +static void omap_mcbsp_tx_newdata(struct omap_mcbsp_s *s) +{ + s->spcr[1] |= 1 << 1; /* XRDY */ + qemu_irq_raise(s->txdrq); + omap_mcbsp_intr_update(s); +} + +static void omap_mcbsp_sink_tick(void *opaque) +{ + struct omap_mcbsp_s *s = (struct omap_mcbsp_s *) opaque; + static const int bps[8] = { 0, 1, 1, 2, 2, 2, -255, -255 }; + + if (!s->tx_rate) + return; + if (s->tx_req) + printf("%s: Tx FIFO underrun\n", __FUNCTION__); + + s->tx_req = s->tx_rate << bps[(s->xcr[0] >> 5) & 7]; + + omap_mcbsp_tx_newdata(s); + timer_mod(s->sink_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + get_ticks_per_sec()); +} + +static void omap_mcbsp_tx_start(struct omap_mcbsp_s *s) +{ + if (!s->codec || !s->codec->cts) + omap_mcbsp_sink_tick(s); + else if (s->codec->out.size) { + s->tx_req = s->codec->out.size; + omap_mcbsp_tx_newdata(s); + } +} + +static void omap_mcbsp_tx_done(struct omap_mcbsp_s *s) +{ + s->spcr[1] &= ~(1 << 1); /* XRDY */ + qemu_irq_lower(s->txdrq); + omap_mcbsp_intr_update(s); + if (s->codec && s->codec->cts) + s->codec->tx_swallow(s->codec->opaque); +} + +static void omap_mcbsp_tx_stop(struct omap_mcbsp_s *s) +{ + s->tx_req = 0; + omap_mcbsp_tx_done(s); + timer_del(s->sink_timer); +} + +static void omap_mcbsp_req_update(struct omap_mcbsp_s *s) +{ + int prev_rx_rate, prev_tx_rate; + int rx_rate = 0, tx_rate = 0; + int cpu_rate = 1500000; /* XXX */ + + /* TODO: check CLKSTP bit */ + if (s->spcr[1] & (1 << 6)) { /* GRST */ + if (s->spcr[0] & (1 << 0)) { /* RRST */ + if ((s->srgr[1] & (1 << 13)) && /* CLKSM */ + (s->pcr & (1 << 8))) { /* CLKRM */ + if (~s->pcr & (1 << 7)) /* SCLKME */ + rx_rate = cpu_rate / + ((s->srgr[0] & 0xff) + 1); /* CLKGDV */ + } else + if (s->codec) + rx_rate = s->codec->rx_rate; + } + + if (s->spcr[1] & (1 << 0)) { /* XRST */ + if ((s->srgr[1] & (1 << 13)) && /* CLKSM */ + (s->pcr & (1 << 9))) { /* CLKXM */ + if (~s->pcr & (1 << 7)) /* SCLKME */ + tx_rate = cpu_rate / + ((s->srgr[0] & 0xff) + 1); /* CLKGDV */ + } else + if (s->codec) + tx_rate = s->codec->tx_rate; + } + } + prev_tx_rate = s->tx_rate; + prev_rx_rate = s->rx_rate; + s->tx_rate = tx_rate; + s->rx_rate = rx_rate; + + if (s->codec) + s->codec->set_rate(s->codec->opaque, rx_rate, tx_rate); + + if (!prev_tx_rate && tx_rate) + omap_mcbsp_tx_start(s); + else if (s->tx_rate && !tx_rate) + omap_mcbsp_tx_stop(s); + + if (!prev_rx_rate && rx_rate) + omap_mcbsp_rx_start(s); + else if (prev_tx_rate && !tx_rate) + omap_mcbsp_rx_stop(s); +} + +static uint64_t omap_mcbsp_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_mcbsp_s *s = (struct omap_mcbsp_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + uint16_t ret; + + if (size != 2) { + return omap_badwidth_read16(opaque, addr); + } + + switch (offset) { + case 0x00: /* DRR2 */ + if (((s->rcr[0] >> 5) & 7) < 3) /* RWDLEN1 */ + return 0x0000; + /* Fall through. */ + case 0x02: /* DRR1 */ + if (s->rx_req < 2) { + printf("%s: Rx FIFO underrun\n", __FUNCTION__); + omap_mcbsp_rx_done(s); + } else { + s->tx_req -= 2; + if (s->codec && s->codec->in.len >= 2) { + ret = s->codec->in.fifo[s->codec->in.start ++] << 8; + ret |= s->codec->in.fifo[s->codec->in.start ++]; + s->codec->in.len -= 2; + } else + ret = 0x0000; + if (!s->tx_req) + omap_mcbsp_rx_done(s); + return ret; + } + return 0x0000; + + case 0x04: /* DXR2 */ + case 0x06: /* DXR1 */ + return 0x0000; + + case 0x08: /* SPCR2 */ + return s->spcr[1]; + case 0x0a: /* SPCR1 */ + return s->spcr[0]; + case 0x0c: /* RCR2 */ + return s->rcr[1]; + case 0x0e: /* RCR1 */ + return s->rcr[0]; + case 0x10: /* XCR2 */ + return s->xcr[1]; + case 0x12: /* XCR1 */ + return s->xcr[0]; + case 0x14: /* SRGR2 */ + return s->srgr[1]; + case 0x16: /* SRGR1 */ + return s->srgr[0]; + case 0x18: /* MCR2 */ + return s->mcr[1]; + case 0x1a: /* MCR1 */ + return s->mcr[0]; + case 0x1c: /* RCERA */ + return s->rcer[0]; + case 0x1e: /* RCERB */ + return s->rcer[1]; + case 0x20: /* XCERA */ + return s->xcer[0]; + case 0x22: /* XCERB */ + return s->xcer[1]; + case 0x24: /* PCR0 */ + return s->pcr; + case 0x26: /* RCERC */ + return s->rcer[2]; + case 0x28: /* RCERD */ + return s->rcer[3]; + case 0x2a: /* XCERC */ + return s->xcer[2]; + case 0x2c: /* XCERD */ + return s->xcer[3]; + case 0x2e: /* RCERE */ + return s->rcer[4]; + case 0x30: /* RCERF */ + return s->rcer[5]; + case 0x32: /* XCERE */ + return s->xcer[4]; + case 0x34: /* XCERF */ + return s->xcer[5]; + case 0x36: /* RCERG */ + return s->rcer[6]; + case 0x38: /* RCERH */ + return s->rcer[7]; + case 0x3a: /* XCERG */ + return s->xcer[6]; + case 0x3c: /* XCERH */ + return s->xcer[7]; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_mcbsp_writeh(void *opaque, hwaddr addr, + uint32_t value) +{ + struct omap_mcbsp_s *s = (struct omap_mcbsp_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + switch (offset) { + case 0x00: /* DRR2 */ + case 0x02: /* DRR1 */ + OMAP_RO_REG(addr); + return; + + case 0x04: /* DXR2 */ + if (((s->xcr[0] >> 5) & 7) < 3) /* XWDLEN1 */ + return; + /* Fall through. */ + case 0x06: /* DXR1 */ + if (s->tx_req > 1) { + s->tx_req -= 2; + if (s->codec && s->codec->cts) { + s->codec->out.fifo[s->codec->out.len ++] = (value >> 8) & 0xff; + s->codec->out.fifo[s->codec->out.len ++] = (value >> 0) & 0xff; + } + if (s->tx_req < 2) + omap_mcbsp_tx_done(s); + } else + printf("%s: Tx FIFO overrun\n", __FUNCTION__); + return; + + case 0x08: /* SPCR2 */ + s->spcr[1] &= 0x0002; + s->spcr[1] |= 0x03f9 & value; + s->spcr[1] |= 0x0004 & (value << 2); /* XEMPTY := XRST */ + if (~value & 1) /* XRST */ + s->spcr[1] &= ~6; + omap_mcbsp_req_update(s); + return; + case 0x0a: /* SPCR1 */ + s->spcr[0] &= 0x0006; + s->spcr[0] |= 0xf8f9 & value; + if (value & (1 << 15)) /* DLB */ + printf("%s: Digital Loopback mode enable attempt\n", __FUNCTION__); + if (~value & 1) { /* RRST */ + s->spcr[0] &= ~6; + s->rx_req = 0; + omap_mcbsp_rx_done(s); + } + omap_mcbsp_req_update(s); + return; + + case 0x0c: /* RCR2 */ + s->rcr[1] = value & 0xffff; + return; + case 0x0e: /* RCR1 */ + s->rcr[0] = value & 0x7fe0; + return; + case 0x10: /* XCR2 */ + s->xcr[1] = value & 0xffff; + return; + case 0x12: /* XCR1 */ + s->xcr[0] = value & 0x7fe0; + return; + case 0x14: /* SRGR2 */ + s->srgr[1] = value & 0xffff; + omap_mcbsp_req_update(s); + return; + case 0x16: /* SRGR1 */ + s->srgr[0] = value & 0xffff; + omap_mcbsp_req_update(s); + return; + case 0x18: /* MCR2 */ + s->mcr[1] = value & 0x03e3; + if (value & 3) /* XMCM */ + printf("%s: Tx channel selection mode enable attempt\n", + __FUNCTION__); + return; + case 0x1a: /* MCR1 */ + s->mcr[0] = value & 0x03e1; + if (value & 1) /* RMCM */ + printf("%s: Rx channel selection mode enable attempt\n", + __FUNCTION__); + return; + case 0x1c: /* RCERA */ + s->rcer[0] = value & 0xffff; + return; + case 0x1e: /* RCERB */ + s->rcer[1] = value & 0xffff; + return; + case 0x20: /* XCERA */ + s->xcer[0] = value & 0xffff; + return; + case 0x22: /* XCERB */ + s->xcer[1] = value & 0xffff; + return; + case 0x24: /* PCR0 */ + s->pcr = value & 0x7faf; + return; + case 0x26: /* RCERC */ + s->rcer[2] = value & 0xffff; + return; + case 0x28: /* RCERD */ + s->rcer[3] = value & 0xffff; + return; + case 0x2a: /* XCERC */ + s->xcer[2] = value & 0xffff; + return; + case 0x2c: /* XCERD */ + s->xcer[3] = value & 0xffff; + return; + case 0x2e: /* RCERE */ + s->rcer[4] = value & 0xffff; + return; + case 0x30: /* RCERF */ + s->rcer[5] = value & 0xffff; + return; + case 0x32: /* XCERE */ + s->xcer[4] = value & 0xffff; + return; + case 0x34: /* XCERF */ + s->xcer[5] = value & 0xffff; + return; + case 0x36: /* RCERG */ + s->rcer[6] = value & 0xffff; + return; + case 0x38: /* RCERH */ + s->rcer[7] = value & 0xffff; + return; + case 0x3a: /* XCERG */ + s->xcer[6] = value & 0xffff; + return; + case 0x3c: /* XCERH */ + s->xcer[7] = value & 0xffff; + return; + } + + OMAP_BAD_REG(addr); +} + +static void omap_mcbsp_writew(void *opaque, hwaddr addr, + uint32_t value) +{ + struct omap_mcbsp_s *s = (struct omap_mcbsp_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + if (offset == 0x04) { /* DXR */ + if (((s->xcr[0] >> 5) & 7) < 3) /* XWDLEN1 */ + return; + if (s->tx_req > 3) { + s->tx_req -= 4; + if (s->codec && s->codec->cts) { + s->codec->out.fifo[s->codec->out.len ++] = + (value >> 24) & 0xff; + s->codec->out.fifo[s->codec->out.len ++] = + (value >> 16) & 0xff; + s->codec->out.fifo[s->codec->out.len ++] = + (value >> 8) & 0xff; + s->codec->out.fifo[s->codec->out.len ++] = + (value >> 0) & 0xff; + } + if (s->tx_req < 4) + omap_mcbsp_tx_done(s); + } else + printf("%s: Tx FIFO overrun\n", __FUNCTION__); + return; + } + + omap_badwidth_write16(opaque, addr, value); +} + +static void omap_mcbsp_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + switch (size) { + case 2: + omap_mcbsp_writeh(opaque, addr, value); + break; + case 4: + omap_mcbsp_writew(opaque, addr, value); + break; + default: + omap_badwidth_write16(opaque, addr, value); + } +} + +static const MemoryRegionOps omap_mcbsp_ops = { + .read = omap_mcbsp_read, + .write = omap_mcbsp_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_mcbsp_reset(struct omap_mcbsp_s *s) +{ + memset(&s->spcr, 0, sizeof(s->spcr)); + memset(&s->rcr, 0, sizeof(s->rcr)); + memset(&s->xcr, 0, sizeof(s->xcr)); + s->srgr[0] = 0x0001; + s->srgr[1] = 0x2000; + memset(&s->mcr, 0, sizeof(s->mcr)); + memset(&s->pcr, 0, sizeof(s->pcr)); + memset(&s->rcer, 0, sizeof(s->rcer)); + memset(&s->xcer, 0, sizeof(s->xcer)); + s->tx_req = 0; + s->rx_req = 0; + s->tx_rate = 0; + s->rx_rate = 0; + timer_del(s->source_timer); + timer_del(s->sink_timer); +} + +static struct omap_mcbsp_s *omap_mcbsp_init(MemoryRegion *system_memory, + hwaddr base, + qemu_irq txirq, qemu_irq rxirq, + qemu_irq *dma, omap_clk clk) +{ + struct omap_mcbsp_s *s = (struct omap_mcbsp_s *) + g_malloc0(sizeof(struct omap_mcbsp_s)); + + s->txirq = txirq; + s->rxirq = rxirq; + s->txdrq = dma[0]; + s->rxdrq = dma[1]; + s->sink_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, omap_mcbsp_sink_tick, s); + s->source_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, omap_mcbsp_source_tick, s); + omap_mcbsp_reset(s); + + memory_region_init_io(&s->iomem, NULL, &omap_mcbsp_ops, s, "omap-mcbsp", 0x800); + memory_region_add_subregion(system_memory, base, &s->iomem); + + return s; +} + +static void omap_mcbsp_i2s_swallow(void *opaque, int line, int level) +{ + struct omap_mcbsp_s *s = (struct omap_mcbsp_s *) opaque; + + if (s->rx_rate) { + s->rx_req = s->codec->in.len; + omap_mcbsp_rx_newdata(s); + } +} + +static void omap_mcbsp_i2s_start(void *opaque, int line, int level) +{ + struct omap_mcbsp_s *s = (struct omap_mcbsp_s *) opaque; + + if (s->tx_rate) { + s->tx_req = s->codec->out.size; + omap_mcbsp_tx_newdata(s); + } +} + +void omap_mcbsp_i2s_attach(struct omap_mcbsp_s *s, I2SCodec *slave) +{ + s->codec = slave; + slave->rx_swallow = qemu_allocate_irq(omap_mcbsp_i2s_swallow, s, 0); + slave->tx_start = qemu_allocate_irq(omap_mcbsp_i2s_start, s, 0); +} + +/* LED Pulse Generators */ +struct omap_lpg_s { + MemoryRegion iomem; + QEMUTimer *tm; + + uint8_t control; + uint8_t power; + int64_t on; + int64_t period; + int clk; + int cycle; +}; + +static void omap_lpg_tick(void *opaque) +{ + struct omap_lpg_s *s = opaque; + + if (s->cycle) + timer_mod(s->tm, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + s->period - s->on); + else + timer_mod(s->tm, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + s->on); + + s->cycle = !s->cycle; + printf("%s: LED is %s\n", __FUNCTION__, s->cycle ? "on" : "off"); +} + +static void omap_lpg_update(struct omap_lpg_s *s) +{ + int64_t on, period = 1, ticks = 1000; + static const int per[8] = { 1, 2, 4, 8, 12, 16, 20, 24 }; + + if (~s->control & (1 << 6)) /* LPGRES */ + on = 0; + else if (s->control & (1 << 7)) /* PERM_ON */ + on = period; + else { + period = muldiv64(ticks, per[s->control & 7], /* PERCTRL */ + 256 / 32); + on = (s->clk && s->power) ? muldiv64(ticks, + per[(s->control >> 3) & 7], 256) : 0; /* ONCTRL */ + } + + timer_del(s->tm); + if (on == period && s->on < s->period) + printf("%s: LED is on\n", __FUNCTION__); + else if (on == 0 && s->on) + printf("%s: LED is off\n", __FUNCTION__); + else if (on && (on != s->on || period != s->period)) { + s->cycle = 0; + s->on = on; + s->period = period; + omap_lpg_tick(s); + return; + } + + s->on = on; + s->period = period; +} + +static void omap_lpg_reset(struct omap_lpg_s *s) +{ + s->control = 0x00; + s->power = 0x00; + s->clk = 1; + omap_lpg_update(s); +} + +static uint64_t omap_lpg_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_lpg_s *s = (struct omap_lpg_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + if (size != 1) { + return omap_badwidth_read8(opaque, addr); + } + + switch (offset) { + case 0x00: /* LCR */ + return s->control; + + case 0x04: /* PMR */ + return s->power; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_lpg_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_lpg_s *s = (struct omap_lpg_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + if (size != 1) { + omap_badwidth_write8(opaque, addr, value); + return; + } + + switch (offset) { + case 0x00: /* LCR */ + if (~value & (1 << 6)) /* LPGRES */ + omap_lpg_reset(s); + s->control = value & 0xff; + omap_lpg_update(s); + return; + + case 0x04: /* PMR */ + s->power = value & 0x01; + omap_lpg_update(s); + return; + + default: + OMAP_BAD_REG(addr); + return; + } +} + +static const MemoryRegionOps omap_lpg_ops = { + .read = omap_lpg_read, + .write = omap_lpg_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_lpg_clk_update(void *opaque, int line, int on) +{ + struct omap_lpg_s *s = (struct omap_lpg_s *) opaque; + + s->clk = on; + omap_lpg_update(s); +} + +static struct omap_lpg_s *omap_lpg_init(MemoryRegion *system_memory, + hwaddr base, omap_clk clk) +{ + struct omap_lpg_s *s = (struct omap_lpg_s *) + g_malloc0(sizeof(struct omap_lpg_s)); + + s->tm = timer_new_ms(QEMU_CLOCK_VIRTUAL, omap_lpg_tick, s); + + omap_lpg_reset(s); + + memory_region_init_io(&s->iomem, NULL, &omap_lpg_ops, s, "omap-lpg", 0x800); + memory_region_add_subregion(system_memory, base, &s->iomem); + + omap_clk_adduser(clk, qemu_allocate_irq(omap_lpg_clk_update, s, 0)); + + return s; +} + +/* MPUI Peripheral Bridge configuration */ +static uint64_t omap_mpui_io_read(void *opaque, hwaddr addr, + unsigned size) +{ + if (size != 2) { + return omap_badwidth_read16(opaque, addr); + } + + if (addr == OMAP_MPUI_BASE) /* CMR */ + return 0xfe4d; + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_mpui_io_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + /* FIXME: infinite loop */ + omap_badwidth_write16(opaque, addr, value); +} + +static const MemoryRegionOps omap_mpui_io_ops = { + .read = omap_mpui_io_read, + .write = omap_mpui_io_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_setup_mpui_io(MemoryRegion *system_memory, + struct omap_mpu_state_s *mpu) +{ + memory_region_init_io(&mpu->mpui_io_iomem, NULL, &omap_mpui_io_ops, mpu, + "omap-mpui-io", 0x7fff); + memory_region_add_subregion(system_memory, OMAP_MPUI_BASE, + &mpu->mpui_io_iomem); +} + +/* General chip reset */ +static void omap1_mpu_reset(void *opaque) +{ + struct omap_mpu_state_s *mpu = (struct omap_mpu_state_s *) opaque; + + omap_dma_reset(mpu->dma); + omap_mpu_timer_reset(mpu->timer[0]); + omap_mpu_timer_reset(mpu->timer[1]); + omap_mpu_timer_reset(mpu->timer[2]); + omap_wd_timer_reset(mpu->wdt); + omap_os_timer_reset(mpu->os_timer); + omap_lcdc_reset(mpu->lcd); + omap_ulpd_pm_reset(mpu); + omap_pin_cfg_reset(mpu); + omap_mpui_reset(mpu); + omap_tipb_bridge_reset(mpu->private_tipb); + omap_tipb_bridge_reset(mpu->public_tipb); + omap_dpll_reset(mpu->dpll[0]); + omap_dpll_reset(mpu->dpll[1]); + omap_dpll_reset(mpu->dpll[2]); + omap_uart_reset(mpu->uart[0]); + omap_uart_reset(mpu->uart[1]); + omap_uart_reset(mpu->uart[2]); + omap_mmc_reset(mpu->mmc); + omap_mpuio_reset(mpu->mpuio); + omap_uwire_reset(mpu->microwire); + omap_pwl_reset(mpu->pwl); + omap_pwt_reset(mpu->pwt); + omap_rtc_reset(mpu->rtc); + omap_mcbsp_reset(mpu->mcbsp1); + omap_mcbsp_reset(mpu->mcbsp2); + omap_mcbsp_reset(mpu->mcbsp3); + omap_lpg_reset(mpu->led[0]); + omap_lpg_reset(mpu->led[1]); + omap_clkm_reset(mpu); + cpu_reset(CPU(mpu->cpu)); +} + +static const struct omap_map_s { + hwaddr phys_dsp; + hwaddr phys_mpu; + uint32_t size; + const char *name; +} omap15xx_dsp_mm[] = { + /* Strobe 0 */ + { 0xe1010000, 0xfffb0000, 0x800, "UART1 BT" }, /* CS0 */ + { 0xe1010800, 0xfffb0800, 0x800, "UART2 COM" }, /* CS1 */ + { 0xe1011800, 0xfffb1800, 0x800, "McBSP1 audio" }, /* CS3 */ + { 0xe1012000, 0xfffb2000, 0x800, "MCSI2 communication" }, /* CS4 */ + { 0xe1012800, 0xfffb2800, 0x800, "MCSI1 BT u-Law" }, /* CS5 */ + { 0xe1013000, 0xfffb3000, 0x800, "uWire" }, /* CS6 */ + { 0xe1013800, 0xfffb3800, 0x800, "I^2C" }, /* CS7 */ + { 0xe1014000, 0xfffb4000, 0x800, "USB W2FC" }, /* CS8 */ + { 0xe1014800, 0xfffb4800, 0x800, "RTC" }, /* CS9 */ + { 0xe1015000, 0xfffb5000, 0x800, "MPUIO" }, /* CS10 */ + { 0xe1015800, 0xfffb5800, 0x800, "PWL" }, /* CS11 */ + { 0xe1016000, 0xfffb6000, 0x800, "PWT" }, /* CS12 */ + { 0xe1017000, 0xfffb7000, 0x800, "McBSP3" }, /* CS14 */ + { 0xe1017800, 0xfffb7800, 0x800, "MMC" }, /* CS15 */ + { 0xe1019000, 0xfffb9000, 0x800, "32-kHz timer" }, /* CS18 */ + { 0xe1019800, 0xfffb9800, 0x800, "UART3" }, /* CS19 */ + { 0xe101c800, 0xfffbc800, 0x800, "TIPB switches" }, /* CS25 */ + /* Strobe 1 */ + { 0xe101e000, 0xfffce000, 0x800, "GPIOs" }, /* CS28 */ + + { 0 } +}; + +static void omap_setup_dsp_mapping(MemoryRegion *system_memory, + const struct omap_map_s *map) +{ + MemoryRegion *io; + + for (; map->phys_dsp; map ++) { + io = g_new(MemoryRegion, 1); + memory_region_init_alias(io, NULL, map->name, + system_memory, map->phys_mpu, map->size); + memory_region_add_subregion(system_memory, map->phys_dsp, io); + } +} + +void omap_mpu_wakeup(void *opaque, int irq, int req) +{ + struct omap_mpu_state_s *mpu = (struct omap_mpu_state_s *) opaque; + CPUState *cpu = CPU(mpu->cpu); + + if (cpu->halted) { + cpu_interrupt(cpu, CPU_INTERRUPT_EXITTB); + } +} + +static const struct dma_irq_map omap1_dma_irq_map[] = { + { 0, OMAP_INT_DMA_CH0_6 }, + { 0, OMAP_INT_DMA_CH1_7 }, + { 0, OMAP_INT_DMA_CH2_8 }, + { 0, OMAP_INT_DMA_CH3 }, + { 0, OMAP_INT_DMA_CH4 }, + { 0, OMAP_INT_DMA_CH5 }, + { 1, OMAP_INT_1610_DMA_CH6 }, + { 1, OMAP_INT_1610_DMA_CH7 }, + { 1, OMAP_INT_1610_DMA_CH8 }, + { 1, OMAP_INT_1610_DMA_CH9 }, + { 1, OMAP_INT_1610_DMA_CH10 }, + { 1, OMAP_INT_1610_DMA_CH11 }, + { 1, OMAP_INT_1610_DMA_CH12 }, + { 1, OMAP_INT_1610_DMA_CH13 }, + { 1, OMAP_INT_1610_DMA_CH14 }, + { 1, OMAP_INT_1610_DMA_CH15 } +}; + +/* DMA ports for OMAP1 */ +static int omap_validate_emiff_addr(struct omap_mpu_state_s *s, + hwaddr addr) +{ + return range_covers_byte(OMAP_EMIFF_BASE, s->sdram_size, addr); +} + +static int omap_validate_emifs_addr(struct omap_mpu_state_s *s, + hwaddr addr) +{ + return range_covers_byte(OMAP_EMIFS_BASE, OMAP_EMIFF_BASE - OMAP_EMIFS_BASE, + addr); +} + +static int omap_validate_imif_addr(struct omap_mpu_state_s *s, + hwaddr addr) +{ + return range_covers_byte(OMAP_IMIF_BASE, s->sram_size, addr); +} + +static int omap_validate_tipb_addr(struct omap_mpu_state_s *s, + hwaddr addr) +{ + return range_covers_byte(0xfffb0000, 0xffff0000 - 0xfffb0000, addr); +} + +static int omap_validate_local_addr(struct omap_mpu_state_s *s, + hwaddr addr) +{ + return range_covers_byte(OMAP_LOCALBUS_BASE, 0x1000000, addr); +} + +static int omap_validate_tipb_mpui_addr(struct omap_mpu_state_s *s, + hwaddr addr) +{ + return range_covers_byte(0xe1010000, 0xe1020004 - 0xe1010000, addr); +} + +struct omap_mpu_state_s *omap310_mpu_init(MemoryRegion *system_memory, + unsigned long sdram_size, + const char *core) +{ + int i; + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) + g_malloc0(sizeof(struct omap_mpu_state_s)); + qemu_irq dma_irqs[6]; + DriveInfo *dinfo; + SysBusDevice *busdev; + + if (!core) + core = "ti925t"; + + /* Core */ + s->mpu_model = omap310; + s->cpu = cpu_arm_init(core); + if (s->cpu == NULL) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + s->sdram_size = sdram_size; + s->sram_size = OMAP15XX_SRAM_SIZE; + + s->wakeup = qemu_allocate_irq(omap_mpu_wakeup, s, 0); + + /* Clocks */ + omap_clk_init(s); + + /* Memory-mapped stuff */ + memory_region_allocate_system_memory(&s->emiff_ram, NULL, "omap1.dram", + s->sdram_size); + memory_region_add_subregion(system_memory, OMAP_EMIFF_BASE, &s->emiff_ram); + memory_region_init_ram(&s->imif_ram, NULL, "omap1.sram", s->sram_size, + &error_abort); + vmstate_register_ram_global(&s->imif_ram); + memory_region_add_subregion(system_memory, OMAP_IMIF_BASE, &s->imif_ram); + + omap_clkm_init(system_memory, 0xfffece00, 0xe1008000, s); + + s->ih[0] = qdev_create(NULL, "omap-intc"); + qdev_prop_set_uint32(s->ih[0], "size", 0x100); + qdev_prop_set_ptr(s->ih[0], "clk", omap_findclk(s, "arminth_ck")); + qdev_init_nofail(s->ih[0]); + busdev = SYS_BUS_DEVICE(s->ih[0]); + sysbus_connect_irq(busdev, 0, + qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_IRQ)); + sysbus_connect_irq(busdev, 1, + qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_FIQ)); + sysbus_mmio_map(busdev, 0, 0xfffecb00); + s->ih[1] = qdev_create(NULL, "omap-intc"); + qdev_prop_set_uint32(s->ih[1], "size", 0x800); + qdev_prop_set_ptr(s->ih[1], "clk", omap_findclk(s, "arminth_ck")); + qdev_init_nofail(s->ih[1]); + busdev = SYS_BUS_DEVICE(s->ih[1]); + sysbus_connect_irq(busdev, 0, + qdev_get_gpio_in(s->ih[0], OMAP_INT_15XX_IH2_IRQ)); + /* The second interrupt controller's FIQ output is not wired up */ + sysbus_mmio_map(busdev, 0, 0xfffe0000); + + for (i = 0; i < 6; i++) { + dma_irqs[i] = qdev_get_gpio_in(s->ih[omap1_dma_irq_map[i].ih], + omap1_dma_irq_map[i].intr); + } + s->dma = omap_dma_init(0xfffed800, dma_irqs, system_memory, + qdev_get_gpio_in(s->ih[0], OMAP_INT_DMA_LCD), + s, omap_findclk(s, "dma_ck"), omap_dma_3_1); + + s->port[emiff ].addr_valid = omap_validate_emiff_addr; + s->port[emifs ].addr_valid = omap_validate_emifs_addr; + s->port[imif ].addr_valid = omap_validate_imif_addr; + s->port[tipb ].addr_valid = omap_validate_tipb_addr; + s->port[local ].addr_valid = omap_validate_local_addr; + s->port[tipb_mpui].addr_valid = omap_validate_tipb_mpui_addr; + + /* Register SDRAM and SRAM DMA ports for fast transfers. */ + soc_dma_port_add_mem(s->dma, memory_region_get_ram_ptr(&s->emiff_ram), + OMAP_EMIFF_BASE, s->sdram_size); + soc_dma_port_add_mem(s->dma, memory_region_get_ram_ptr(&s->imif_ram), + OMAP_IMIF_BASE, s->sram_size); + + s->timer[0] = omap_mpu_timer_init(system_memory, 0xfffec500, + qdev_get_gpio_in(s->ih[0], OMAP_INT_TIMER1), + omap_findclk(s, "mputim_ck")); + s->timer[1] = omap_mpu_timer_init(system_memory, 0xfffec600, + qdev_get_gpio_in(s->ih[0], OMAP_INT_TIMER2), + omap_findclk(s, "mputim_ck")); + s->timer[2] = omap_mpu_timer_init(system_memory, 0xfffec700, + qdev_get_gpio_in(s->ih[0], OMAP_INT_TIMER3), + omap_findclk(s, "mputim_ck")); + + s->wdt = omap_wd_timer_init(system_memory, 0xfffec800, + qdev_get_gpio_in(s->ih[0], OMAP_INT_WD_TIMER), + omap_findclk(s, "armwdt_ck")); + + s->os_timer = omap_os_timer_init(system_memory, 0xfffb9000, + qdev_get_gpio_in(s->ih[1], OMAP_INT_OS_TIMER), + omap_findclk(s, "clk32-kHz")); + + s->lcd = omap_lcdc_init(system_memory, 0xfffec000, + qdev_get_gpio_in(s->ih[0], OMAP_INT_LCD_CTRL), + omap_dma_get_lcdch(s->dma), + omap_findclk(s, "lcd_ck")); + + omap_ulpd_pm_init(system_memory, 0xfffe0800, s); + omap_pin_cfg_init(system_memory, 0xfffe1000, s); + omap_id_init(system_memory, s); + + omap_mpui_init(system_memory, 0xfffec900, s); + + s->private_tipb = omap_tipb_bridge_init(system_memory, 0xfffeca00, + qdev_get_gpio_in(s->ih[0], OMAP_INT_BRIDGE_PRIV), + omap_findclk(s, "tipb_ck")); + s->public_tipb = omap_tipb_bridge_init(system_memory, 0xfffed300, + qdev_get_gpio_in(s->ih[0], OMAP_INT_BRIDGE_PUB), + omap_findclk(s, "tipb_ck")); + + omap_tcmi_init(system_memory, 0xfffecc00, s); + + s->uart[0] = omap_uart_init(0xfffb0000, + qdev_get_gpio_in(s->ih[1], OMAP_INT_UART1), + omap_findclk(s, "uart1_ck"), + omap_findclk(s, "uart1_ck"), + s->drq[OMAP_DMA_UART1_TX], s->drq[OMAP_DMA_UART1_RX], + "uart1", + serial_hds[0]); + s->uart[1] = omap_uart_init(0xfffb0800, + qdev_get_gpio_in(s->ih[1], OMAP_INT_UART2), + omap_findclk(s, "uart2_ck"), + omap_findclk(s, "uart2_ck"), + s->drq[OMAP_DMA_UART2_TX], s->drq[OMAP_DMA_UART2_RX], + "uart2", + serial_hds[0] ? serial_hds[1] : NULL); + s->uart[2] = omap_uart_init(0xfffb9800, + qdev_get_gpio_in(s->ih[0], OMAP_INT_UART3), + omap_findclk(s, "uart3_ck"), + omap_findclk(s, "uart3_ck"), + s->drq[OMAP_DMA_UART3_TX], s->drq[OMAP_DMA_UART3_RX], + "uart3", + serial_hds[0] && serial_hds[1] ? serial_hds[2] : NULL); + + s->dpll[0] = omap_dpll_init(system_memory, 0xfffecf00, + omap_findclk(s, "dpll1")); + s->dpll[1] = omap_dpll_init(system_memory, 0xfffed000, + omap_findclk(s, "dpll2")); + s->dpll[2] = omap_dpll_init(system_memory, 0xfffed100, + omap_findclk(s, "dpll3")); + + dinfo = drive_get(IF_SD, 0, 0); + if (!dinfo) { + fprintf(stderr, "qemu: missing SecureDigital device\n"); + exit(1); + } + s->mmc = omap_mmc_init(0xfffb7800, system_memory, + blk_by_legacy_dinfo(dinfo), + qdev_get_gpio_in(s->ih[1], OMAP_INT_OQN), + &s->drq[OMAP_DMA_MMC_TX], + omap_findclk(s, "mmc_ck")); + + s->mpuio = omap_mpuio_init(system_memory, 0xfffb5000, + qdev_get_gpio_in(s->ih[1], OMAP_INT_KEYBOARD), + qdev_get_gpio_in(s->ih[1], OMAP_INT_MPUIO), + s->wakeup, omap_findclk(s, "clk32-kHz")); + + s->gpio = qdev_create(NULL, "omap-gpio"); + qdev_prop_set_int32(s->gpio, "mpu_model", s->mpu_model); + qdev_prop_set_ptr(s->gpio, "clk", omap_findclk(s, "arm_gpio_ck")); + qdev_init_nofail(s->gpio); + sysbus_connect_irq(SYS_BUS_DEVICE(s->gpio), 0, + qdev_get_gpio_in(s->ih[0], OMAP_INT_GPIO_BANK1)); + sysbus_mmio_map(SYS_BUS_DEVICE(s->gpio), 0, 0xfffce000); + + s->microwire = omap_uwire_init(system_memory, 0xfffb3000, + qdev_get_gpio_in(s->ih[1], OMAP_INT_uWireTX), + qdev_get_gpio_in(s->ih[1], OMAP_INT_uWireRX), + s->drq[OMAP_DMA_UWIRE_TX], omap_findclk(s, "mpuper_ck")); + + s->pwl = omap_pwl_init(system_memory, 0xfffb5800, + omap_findclk(s, "armxor_ck")); + s->pwt = omap_pwt_init(system_memory, 0xfffb6000, + omap_findclk(s, "armxor_ck")); + + s->i2c[0] = qdev_create(NULL, "omap_i2c"); + qdev_prop_set_uint8(s->i2c[0], "revision", 0x11); + qdev_prop_set_ptr(s->i2c[0], "fclk", omap_findclk(s, "mpuper_ck")); + qdev_init_nofail(s->i2c[0]); + busdev = SYS_BUS_DEVICE(s->i2c[0]); + sysbus_connect_irq(busdev, 0, qdev_get_gpio_in(s->ih[1], OMAP_INT_I2C)); + sysbus_connect_irq(busdev, 1, s->drq[OMAP_DMA_I2C_TX]); + sysbus_connect_irq(busdev, 2, s->drq[OMAP_DMA_I2C_RX]); + sysbus_mmio_map(busdev, 0, 0xfffb3800); + + s->rtc = omap_rtc_init(system_memory, 0xfffb4800, + qdev_get_gpio_in(s->ih[1], OMAP_INT_RTC_TIMER), + qdev_get_gpio_in(s->ih[1], OMAP_INT_RTC_ALARM), + omap_findclk(s, "clk32-kHz")); + + s->mcbsp1 = omap_mcbsp_init(system_memory, 0xfffb1800, + qdev_get_gpio_in(s->ih[1], OMAP_INT_McBSP1TX), + qdev_get_gpio_in(s->ih[1], OMAP_INT_McBSP1RX), + &s->drq[OMAP_DMA_MCBSP1_TX], omap_findclk(s, "dspxor_ck")); + s->mcbsp2 = omap_mcbsp_init(system_memory, 0xfffb1000, + qdev_get_gpio_in(s->ih[0], + OMAP_INT_310_McBSP2_TX), + qdev_get_gpio_in(s->ih[0], + OMAP_INT_310_McBSP2_RX), + &s->drq[OMAP_DMA_MCBSP2_TX], omap_findclk(s, "mpuper_ck")); + s->mcbsp3 = omap_mcbsp_init(system_memory, 0xfffb7000, + qdev_get_gpio_in(s->ih[1], OMAP_INT_McBSP3TX), + qdev_get_gpio_in(s->ih[1], OMAP_INT_McBSP3RX), + &s->drq[OMAP_DMA_MCBSP3_TX], omap_findclk(s, "dspxor_ck")); + + s->led[0] = omap_lpg_init(system_memory, + 0xfffbd000, omap_findclk(s, "clk32-kHz")); + s->led[1] = omap_lpg_init(system_memory, + 0xfffbd800, omap_findclk(s, "clk32-kHz")); + + /* Register mappings not currenlty implemented: + * MCSI2 Comm fffb2000 - fffb27ff (not mapped on OMAP310) + * MCSI1 Bluetooth fffb2800 - fffb2fff (not mapped on OMAP310) + * USB W2FC fffb4000 - fffb47ff + * Camera Interface fffb6800 - fffb6fff + * USB Host fffba000 - fffba7ff + * FAC fffba800 - fffbafff + * HDQ/1-Wire fffbc000 - fffbc7ff + * TIPB switches fffbc800 - fffbcfff + * Mailbox fffcf000 - fffcf7ff + * Local bus IF fffec100 - fffec1ff + * Local bus MMU fffec200 - fffec2ff + * DSP MMU fffed200 - fffed2ff + */ + + omap_setup_dsp_mapping(system_memory, omap15xx_dsp_mm); + omap_setup_mpui_io(system_memory, s); + + qemu_register_reset(omap1_mpu_reset, s); + + return s; +} diff --git a/qemu/hw/arm/omap2.c b/qemu/hw/arm/omap2.c new file mode 100644 index 000000000..e39b31729 --- /dev/null +++ b/qemu/hw/arm/omap2.c @@ -0,0 +1,2692 @@ +/* + * TI OMAP processors emulation. + * + * Copyright (C) 2007-2008 Nokia Corporation + * Written by Andrzej Zaborowski <andrew@openedhand.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "sysemu/block-backend.h" +#include "sysemu/blockdev.h" +#include "hw/boards.h" +#include "hw/hw.h" +#include "hw/arm/arm.h" +#include "hw/arm/omap.h" +#include "sysemu/sysemu.h" +#include "qemu/timer.h" +#include "sysemu/char.h" +#include "hw/block/flash.h" +#include "hw/arm/soc_dma.h" +#include "hw/sysbus.h" +#include "audio/audio.h" + +/* Enhanced Audio Controller (CODEC only) */ +struct omap_eac_s { + qemu_irq irq; + MemoryRegion iomem; + + uint16_t sysconfig; + uint8_t config[4]; + uint8_t control; + uint8_t address; + uint16_t data; + uint8_t vtol; + uint8_t vtsl; + uint16_t mixer; + uint16_t gain[4]; + uint8_t att; + uint16_t max[7]; + + struct { + qemu_irq txdrq; + qemu_irq rxdrq; + uint32_t (*txrx)(void *opaque, uint32_t, int); + void *opaque; + +#define EAC_BUF_LEN 1024 + uint32_t rxbuf[EAC_BUF_LEN]; + int rxoff; + int rxlen; + int rxavail; + uint32_t txbuf[EAC_BUF_LEN]; + int txlen; + int txavail; + + int enable; + int rate; + + uint16_t config[4]; + + /* These need to be moved to the actual codec */ + QEMUSoundCard card; + SWVoiceIn *in_voice; + SWVoiceOut *out_voice; + int hw_enable; + } codec; + + struct { + uint8_t control; + uint16_t config; + } modem, bt; +}; + +static inline void omap_eac_interrupt_update(struct omap_eac_s *s) +{ + qemu_set_irq(s->irq, (s->codec.config[1] >> 14) & 1); /* AURDI */ +} + +static inline void omap_eac_in_dmarequest_update(struct omap_eac_s *s) +{ + qemu_set_irq(s->codec.rxdrq, (s->codec.rxavail || s->codec.rxlen) && + ((s->codec.config[1] >> 12) & 1)); /* DMAREN */ +} + +static inline void omap_eac_out_dmarequest_update(struct omap_eac_s *s) +{ + qemu_set_irq(s->codec.txdrq, s->codec.txlen < s->codec.txavail && + ((s->codec.config[1] >> 11) & 1)); /* DMAWEN */ +} + +static inline void omap_eac_in_refill(struct omap_eac_s *s) +{ + int left = MIN(EAC_BUF_LEN - s->codec.rxlen, s->codec.rxavail) << 2; + int start = ((s->codec.rxoff + s->codec.rxlen) & (EAC_BUF_LEN - 1)) << 2; + int leftwrap = MIN(left, (EAC_BUF_LEN << 2) - start); + int recv = 1; + uint8_t *buf = (uint8_t *) s->codec.rxbuf + start; + + left -= leftwrap; + start = 0; + while (leftwrap && (recv = AUD_read(s->codec.in_voice, buf + start, + leftwrap)) > 0) { /* Be defensive */ + start += recv; + leftwrap -= recv; + } + if (recv <= 0) + s->codec.rxavail = 0; + else + s->codec.rxavail -= start >> 2; + s->codec.rxlen += start >> 2; + + if (recv > 0 && left > 0) { + start = 0; + while (left && (recv = AUD_read(s->codec.in_voice, + (uint8_t *) s->codec.rxbuf + start, + left)) > 0) { /* Be defensive */ + start += recv; + left -= recv; + } + if (recv <= 0) + s->codec.rxavail = 0; + else + s->codec.rxavail -= start >> 2; + s->codec.rxlen += start >> 2; + } +} + +static inline void omap_eac_out_empty(struct omap_eac_s *s) +{ + int left = s->codec.txlen << 2; + int start = 0; + int sent = 1; + + while (left && (sent = AUD_write(s->codec.out_voice, + (uint8_t *) s->codec.txbuf + start, + left)) > 0) { /* Be defensive */ + start += sent; + left -= sent; + } + + if (!sent) { + s->codec.txavail = 0; + omap_eac_out_dmarequest_update(s); + } + + if (start) + s->codec.txlen = 0; +} + +static void omap_eac_in_cb(void *opaque, int avail_b) +{ + struct omap_eac_s *s = (struct omap_eac_s *) opaque; + + s->codec.rxavail = avail_b >> 2; + omap_eac_in_refill(s); + /* TODO: possibly discard current buffer if overrun */ + omap_eac_in_dmarequest_update(s); +} + +static void omap_eac_out_cb(void *opaque, int free_b) +{ + struct omap_eac_s *s = (struct omap_eac_s *) opaque; + + s->codec.txavail = free_b >> 2; + if (s->codec.txlen) + omap_eac_out_empty(s); + else + omap_eac_out_dmarequest_update(s); +} + +static void omap_eac_enable_update(struct omap_eac_s *s) +{ + s->codec.enable = !(s->codec.config[1] & 1) && /* EACPWD */ + (s->codec.config[1] & 2) && /* AUDEN */ + s->codec.hw_enable; +} + +static const int omap_eac_fsint[4] = { + 8000, + 11025, + 22050, + 44100, +}; + +static const int omap_eac_fsint2[8] = { + 8000, + 11025, + 22050, + 44100, + 48000, + 0, 0, 0, +}; + +static const int omap_eac_fsint3[16] = { + 8000, + 11025, + 16000, + 22050, + 24000, + 32000, + 44100, + 48000, + 0, 0, 0, 0, 0, 0, 0, 0, +}; + +static void omap_eac_rate_update(struct omap_eac_s *s) +{ + int fsint[3]; + + fsint[2] = (s->codec.config[3] >> 9) & 0xf; + fsint[1] = (s->codec.config[2] >> 0) & 0x7; + fsint[0] = (s->codec.config[0] >> 6) & 0x3; + if (fsint[2] < 0xf) + s->codec.rate = omap_eac_fsint3[fsint[2]]; + else if (fsint[1] < 0x7) + s->codec.rate = omap_eac_fsint2[fsint[1]]; + else + s->codec.rate = omap_eac_fsint[fsint[0]]; +} + +static void omap_eac_volume_update(struct omap_eac_s *s) +{ + /* TODO */ +} + +static void omap_eac_format_update(struct omap_eac_s *s) +{ + struct audsettings fmt; + + /* The hardware buffers at most one sample */ + if (s->codec.rxlen) + s->codec.rxlen = 1; + + if (s->codec.in_voice) { + AUD_set_active_in(s->codec.in_voice, 0); + AUD_close_in(&s->codec.card, s->codec.in_voice); + s->codec.in_voice = NULL; + } + if (s->codec.out_voice) { + omap_eac_out_empty(s); + AUD_set_active_out(s->codec.out_voice, 0); + AUD_close_out(&s->codec.card, s->codec.out_voice); + s->codec.out_voice = NULL; + s->codec.txavail = 0; + } + /* Discard what couldn't be written */ + s->codec.txlen = 0; + + omap_eac_enable_update(s); + if (!s->codec.enable) + return; + + omap_eac_rate_update(s); + fmt.endianness = ((s->codec.config[0] >> 8) & 1); /* LI_BI */ + fmt.nchannels = ((s->codec.config[0] >> 10) & 1) ? 2 : 1; /* MN_ST */ + fmt.freq = s->codec.rate; + /* TODO: signedness possibly depends on the CODEC hardware - or + * does I2S specify it? */ + /* All register writes are 16 bits so we we store 16-bit samples + * in the buffers regardless of AGCFR[B8_16] value. */ + fmt.fmt = AUD_FMT_U16; + + s->codec.in_voice = AUD_open_in(&s->codec.card, s->codec.in_voice, + "eac.codec.in", s, omap_eac_in_cb, &fmt); + s->codec.out_voice = AUD_open_out(&s->codec.card, s->codec.out_voice, + "eac.codec.out", s, omap_eac_out_cb, &fmt); + + omap_eac_volume_update(s); + + AUD_set_active_in(s->codec.in_voice, 1); + AUD_set_active_out(s->codec.out_voice, 1); +} + +static void omap_eac_reset(struct omap_eac_s *s) +{ + s->sysconfig = 0; + s->config[0] = 0x0c; + s->config[1] = 0x09; + s->config[2] = 0xab; + s->config[3] = 0x03; + s->control = 0x00; + s->address = 0x00; + s->data = 0x0000; + s->vtol = 0x00; + s->vtsl = 0x00; + s->mixer = 0x0000; + s->gain[0] = 0xe7e7; + s->gain[1] = 0x6767; + s->gain[2] = 0x6767; + s->gain[3] = 0x6767; + s->att = 0xce; + s->max[0] = 0; + s->max[1] = 0; + s->max[2] = 0; + s->max[3] = 0; + s->max[4] = 0; + s->max[5] = 0; + s->max[6] = 0; + + s->modem.control = 0x00; + s->modem.config = 0x0000; + s->bt.control = 0x00; + s->bt.config = 0x0000; + s->codec.config[0] = 0x0649; + s->codec.config[1] = 0x0000; + s->codec.config[2] = 0x0007; + s->codec.config[3] = 0x1ffc; + s->codec.rxoff = 0; + s->codec.rxlen = 0; + s->codec.txlen = 0; + s->codec.rxavail = 0; + s->codec.txavail = 0; + + omap_eac_format_update(s); + omap_eac_interrupt_update(s); +} + +static uint64_t omap_eac_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_eac_s *s = (struct omap_eac_s *) opaque; + uint32_t ret; + + if (size != 2) { + return omap_badwidth_read16(opaque, addr); + } + + switch (addr) { + case 0x000: /* CPCFR1 */ + return s->config[0]; + case 0x004: /* CPCFR2 */ + return s->config[1]; + case 0x008: /* CPCFR3 */ + return s->config[2]; + case 0x00c: /* CPCFR4 */ + return s->config[3]; + + case 0x010: /* CPTCTL */ + return s->control | ((s->codec.rxavail + s->codec.rxlen > 0) << 7) | + ((s->codec.txlen < s->codec.txavail) << 5); + + case 0x014: /* CPTTADR */ + return s->address; + case 0x018: /* CPTDATL */ + return s->data & 0xff; + case 0x01c: /* CPTDATH */ + return s->data >> 8; + case 0x020: /* CPTVSLL */ + return s->vtol; + case 0x024: /* CPTVSLH */ + return s->vtsl | (3 << 5); /* CRDY1 | CRDY2 */ + case 0x040: /* MPCTR */ + return s->modem.control; + case 0x044: /* MPMCCFR */ + return s->modem.config; + case 0x060: /* BPCTR */ + return s->bt.control; + case 0x064: /* BPMCCFR */ + return s->bt.config; + case 0x080: /* AMSCFR */ + return s->mixer; + case 0x084: /* AMVCTR */ + return s->gain[0]; + case 0x088: /* AM1VCTR */ + return s->gain[1]; + case 0x08c: /* AM2VCTR */ + return s->gain[2]; + case 0x090: /* AM3VCTR */ + return s->gain[3]; + case 0x094: /* ASTCTR */ + return s->att; + case 0x098: /* APD1LCR */ + return s->max[0]; + case 0x09c: /* APD1RCR */ + return s->max[1]; + case 0x0a0: /* APD2LCR */ + return s->max[2]; + case 0x0a4: /* APD2RCR */ + return s->max[3]; + case 0x0a8: /* APD3LCR */ + return s->max[4]; + case 0x0ac: /* APD3RCR */ + return s->max[5]; + case 0x0b0: /* APD4R */ + return s->max[6]; + case 0x0b4: /* ADWR */ + /* This should be write-only? Docs list it as read-only. */ + return 0x0000; + case 0x0b8: /* ADRDR */ + if (likely(s->codec.rxlen > 1)) { + ret = s->codec.rxbuf[s->codec.rxoff ++]; + s->codec.rxlen --; + s->codec.rxoff &= EAC_BUF_LEN - 1; + return ret; + } else if (s->codec.rxlen) { + ret = s->codec.rxbuf[s->codec.rxoff ++]; + s->codec.rxlen --; + s->codec.rxoff &= EAC_BUF_LEN - 1; + if (s->codec.rxavail) + omap_eac_in_refill(s); + omap_eac_in_dmarequest_update(s); + return ret; + } + return 0x0000; + case 0x0bc: /* AGCFR */ + return s->codec.config[0]; + case 0x0c0: /* AGCTR */ + return s->codec.config[1] | ((s->codec.config[1] & 2) << 14); + case 0x0c4: /* AGCFR2 */ + return s->codec.config[2]; + case 0x0c8: /* AGCFR3 */ + return s->codec.config[3]; + case 0x0cc: /* MBPDMACTR */ + case 0x0d0: /* MPDDMARR */ + case 0x0d8: /* MPUDMARR */ + case 0x0e4: /* BPDDMARR */ + case 0x0ec: /* BPUDMARR */ + return 0x0000; + + case 0x100: /* VERSION_NUMBER */ + return 0x0010; + + case 0x104: /* SYSCONFIG */ + return s->sysconfig; + + case 0x108: /* SYSSTATUS */ + return 1 | 0xe; /* RESETDONE | stuff */ + } + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_eac_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_eac_s *s = (struct omap_eac_s *) opaque; + + if (size != 2) { + omap_badwidth_write16(opaque, addr, value); + return; + } + + switch (addr) { + case 0x098: /* APD1LCR */ + case 0x09c: /* APD1RCR */ + case 0x0a0: /* APD2LCR */ + case 0x0a4: /* APD2RCR */ + case 0x0a8: /* APD3LCR */ + case 0x0ac: /* APD3RCR */ + case 0x0b0: /* APD4R */ + case 0x0b8: /* ADRDR */ + case 0x0d0: /* MPDDMARR */ + case 0x0d8: /* MPUDMARR */ + case 0x0e4: /* BPDDMARR */ + case 0x0ec: /* BPUDMARR */ + case 0x100: /* VERSION_NUMBER */ + case 0x108: /* SYSSTATUS */ + OMAP_RO_REG(addr); + return; + + case 0x000: /* CPCFR1 */ + s->config[0] = value & 0xff; + omap_eac_format_update(s); + break; + case 0x004: /* CPCFR2 */ + s->config[1] = value & 0xff; + omap_eac_format_update(s); + break; + case 0x008: /* CPCFR3 */ + s->config[2] = value & 0xff; + omap_eac_format_update(s); + break; + case 0x00c: /* CPCFR4 */ + s->config[3] = value & 0xff; + omap_eac_format_update(s); + break; + + case 0x010: /* CPTCTL */ + /* Assuming TXF and TXE bits are read-only... */ + s->control = value & 0x5f; + omap_eac_interrupt_update(s); + break; + + case 0x014: /* CPTTADR */ + s->address = value & 0xff; + break; + case 0x018: /* CPTDATL */ + s->data &= 0xff00; + s->data |= value & 0xff; + break; + case 0x01c: /* CPTDATH */ + s->data &= 0x00ff; + s->data |= value << 8; + break; + case 0x020: /* CPTVSLL */ + s->vtol = value & 0xf8; + break; + case 0x024: /* CPTVSLH */ + s->vtsl = value & 0x9f; + break; + case 0x040: /* MPCTR */ + s->modem.control = value & 0x8f; + break; + case 0x044: /* MPMCCFR */ + s->modem.config = value & 0x7fff; + break; + case 0x060: /* BPCTR */ + s->bt.control = value & 0x8f; + break; + case 0x064: /* BPMCCFR */ + s->bt.config = value & 0x7fff; + break; + case 0x080: /* AMSCFR */ + s->mixer = value & 0x0fff; + break; + case 0x084: /* AMVCTR */ + s->gain[0] = value & 0xffff; + break; + case 0x088: /* AM1VCTR */ + s->gain[1] = value & 0xff7f; + break; + case 0x08c: /* AM2VCTR */ + s->gain[2] = value & 0xff7f; + break; + case 0x090: /* AM3VCTR */ + s->gain[3] = value & 0xff7f; + break; + case 0x094: /* ASTCTR */ + s->att = value & 0xff; + break; + + case 0x0b4: /* ADWR */ + s->codec.txbuf[s->codec.txlen ++] = value; + if (unlikely(s->codec.txlen == EAC_BUF_LEN || + s->codec.txlen == s->codec.txavail)) { + if (s->codec.txavail) + omap_eac_out_empty(s); + /* Discard what couldn't be written */ + s->codec.txlen = 0; + } + break; + + case 0x0bc: /* AGCFR */ + s->codec.config[0] = value & 0x07ff; + omap_eac_format_update(s); + break; + case 0x0c0: /* AGCTR */ + s->codec.config[1] = value & 0x780f; + omap_eac_format_update(s); + break; + case 0x0c4: /* AGCFR2 */ + s->codec.config[2] = value & 0x003f; + omap_eac_format_update(s); + break; + case 0x0c8: /* AGCFR3 */ + s->codec.config[3] = value & 0xffff; + omap_eac_format_update(s); + break; + case 0x0cc: /* MBPDMACTR */ + case 0x0d4: /* MPDDMAWR */ + case 0x0e0: /* MPUDMAWR */ + case 0x0e8: /* BPDDMAWR */ + case 0x0f0: /* BPUDMAWR */ + break; + + case 0x104: /* SYSCONFIG */ + if (value & (1 << 1)) /* SOFTRESET */ + omap_eac_reset(s); + s->sysconfig = value & 0x31d; + break; + + default: + OMAP_BAD_REG(addr); + return; + } +} + +static const MemoryRegionOps omap_eac_ops = { + .read = omap_eac_read, + .write = omap_eac_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static struct omap_eac_s *omap_eac_init(struct omap_target_agent_s *ta, + qemu_irq irq, qemu_irq *drq, omap_clk fclk, omap_clk iclk) +{ + struct omap_eac_s *s = (struct omap_eac_s *) + g_malloc0(sizeof(struct omap_eac_s)); + + s->irq = irq; + s->codec.rxdrq = *drq ++; + s->codec.txdrq = *drq; + omap_eac_reset(s); + + AUD_register_card("OMAP EAC", &s->codec.card); + + memory_region_init_io(&s->iomem, NULL, &omap_eac_ops, s, "omap.eac", + omap_l4_region_size(ta, 0)); + omap_l4_attach(ta, 0, &s->iomem); + + return s; +} + +/* STI/XTI (emulation interface) console - reverse engineered only */ +struct omap_sti_s { + qemu_irq irq; + MemoryRegion iomem; + MemoryRegion iomem_fifo; + CharDriverState *chr; + + uint32_t sysconfig; + uint32_t systest; + uint32_t irqst; + uint32_t irqen; + uint32_t clkcontrol; + uint32_t serial_config; +}; + +#define STI_TRACE_CONSOLE_CHANNEL 239 +#define STI_TRACE_CONTROL_CHANNEL 253 + +static inline void omap_sti_interrupt_update(struct omap_sti_s *s) +{ + qemu_set_irq(s->irq, s->irqst & s->irqen); +} + +static void omap_sti_reset(struct omap_sti_s *s) +{ + s->sysconfig = 0; + s->irqst = 0; + s->irqen = 0; + s->clkcontrol = 0; + s->serial_config = 0; + + omap_sti_interrupt_update(s); +} + +static uint64_t omap_sti_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_sti_s *s = (struct omap_sti_s *) opaque; + + if (size != 4) { + return omap_badwidth_read32(opaque, addr); + } + + switch (addr) { + case 0x00: /* STI_REVISION */ + return 0x10; + + case 0x10: /* STI_SYSCONFIG */ + return s->sysconfig; + + case 0x14: /* STI_SYSSTATUS / STI_RX_STATUS / XTI_SYSSTATUS */ + return 0x00; + + case 0x18: /* STI_IRQSTATUS */ + return s->irqst; + + case 0x1c: /* STI_IRQSETEN / STI_IRQCLREN */ + return s->irqen; + + case 0x24: /* STI_ER / STI_DR / XTI_TRACESELECT */ + case 0x28: /* STI_RX_DR / XTI_RXDATA */ + /* TODO */ + return 0; + + case 0x2c: /* STI_CLK_CTRL / XTI_SCLKCRTL */ + return s->clkcontrol; + + case 0x30: /* STI_SERIAL_CFG / XTI_SCONFIG */ + return s->serial_config; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_sti_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_sti_s *s = (struct omap_sti_s *) opaque; + + if (size != 4) { + omap_badwidth_write32(opaque, addr, value); + return; + } + + switch (addr) { + case 0x00: /* STI_REVISION */ + case 0x14: /* STI_SYSSTATUS / STI_RX_STATUS / XTI_SYSSTATUS */ + OMAP_RO_REG(addr); + return; + + case 0x10: /* STI_SYSCONFIG */ + if (value & (1 << 1)) /* SOFTRESET */ + omap_sti_reset(s); + s->sysconfig = value & 0xfe; + break; + + case 0x18: /* STI_IRQSTATUS */ + s->irqst &= ~value; + omap_sti_interrupt_update(s); + break; + + case 0x1c: /* STI_IRQSETEN / STI_IRQCLREN */ + s->irqen = value & 0xffff; + omap_sti_interrupt_update(s); + break; + + case 0x2c: /* STI_CLK_CTRL / XTI_SCLKCRTL */ + s->clkcontrol = value & 0xff; + break; + + case 0x30: /* STI_SERIAL_CFG / XTI_SCONFIG */ + s->serial_config = value & 0xff; + break; + + case 0x24: /* STI_ER / STI_DR / XTI_TRACESELECT */ + case 0x28: /* STI_RX_DR / XTI_RXDATA */ + /* TODO */ + return; + + default: + OMAP_BAD_REG(addr); + return; + } +} + +static const MemoryRegionOps omap_sti_ops = { + .read = omap_sti_read, + .write = omap_sti_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static uint64_t omap_sti_fifo_read(void *opaque, hwaddr addr, + unsigned size) +{ + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_sti_fifo_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_sti_s *s = (struct omap_sti_s *) opaque; + int ch = addr >> 6; + uint8_t byte = value; + + if (size != 1) { + omap_badwidth_write8(opaque, addr, size); + return; + } + + if (ch == STI_TRACE_CONTROL_CHANNEL) { + /* Flush channel <i>value</i>. */ + qemu_chr_fe_write(s->chr, (const uint8_t *) "\r", 1); + } else if (ch == STI_TRACE_CONSOLE_CHANNEL || 1) { + if (value == 0xc0 || value == 0xc3) { + /* Open channel <i>ch</i>. */ + } else if (value == 0x00) + qemu_chr_fe_write(s->chr, (const uint8_t *) "\n", 1); + else + qemu_chr_fe_write(s->chr, &byte, 1); + } +} + +static const MemoryRegionOps omap_sti_fifo_ops = { + .read = omap_sti_fifo_read, + .write = omap_sti_fifo_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static struct omap_sti_s *omap_sti_init(struct omap_target_agent_s *ta, + MemoryRegion *sysmem, + hwaddr channel_base, qemu_irq irq, omap_clk clk, + CharDriverState *chr) +{ + struct omap_sti_s *s = (struct omap_sti_s *) + g_malloc0(sizeof(struct omap_sti_s)); + + s->irq = irq; + omap_sti_reset(s); + + s->chr = chr ?: qemu_chr_new("null", "null", NULL); + + memory_region_init_io(&s->iomem, NULL, &omap_sti_ops, s, "omap.sti", + omap_l4_region_size(ta, 0)); + omap_l4_attach(ta, 0, &s->iomem); + + memory_region_init_io(&s->iomem_fifo, NULL, &omap_sti_fifo_ops, s, + "omap.sti.fifo", 0x10000); + memory_region_add_subregion(sysmem, channel_base, &s->iomem_fifo); + + return s; +} + +/* L4 Interconnect */ +#define L4TA(n) (n) +#define L4TAO(n) ((n) + 39) + +static const struct omap_l4_region_s omap_l4_region[125] = { + [ 1] = { 0x40800, 0x800, 32 }, /* Initiator agent */ + [ 2] = { 0x41000, 0x1000, 32 }, /* Link agent */ + [ 0] = { 0x40000, 0x800, 32 }, /* Address and protection */ + [ 3] = { 0x00000, 0x1000, 32 | 16 | 8 }, /* System Control and Pinout */ + [ 4] = { 0x01000, 0x1000, 32 | 16 | 8 }, /* L4TAO1 */ + [ 5] = { 0x04000, 0x1000, 32 | 16 }, /* 32K Timer */ + [ 6] = { 0x05000, 0x1000, 32 | 16 | 8 }, /* L4TAO2 */ + [ 7] = { 0x08000, 0x800, 32 }, /* PRCM Region A */ + [ 8] = { 0x08800, 0x800, 32 }, /* PRCM Region B */ + [ 9] = { 0x09000, 0x1000, 32 | 16 | 8 }, /* L4TAO */ + [ 10] = { 0x12000, 0x1000, 32 | 16 | 8 }, /* Test (BCM) */ + [ 11] = { 0x13000, 0x1000, 32 | 16 | 8 }, /* L4TA1 */ + [ 12] = { 0x14000, 0x1000, 32 }, /* Test/emulation (TAP) */ + [ 13] = { 0x15000, 0x1000, 32 | 16 | 8 }, /* L4TA2 */ + [ 14] = { 0x18000, 0x1000, 32 | 16 | 8 }, /* GPIO1 */ + [ 16] = { 0x1a000, 0x1000, 32 | 16 | 8 }, /* GPIO2 */ + [ 18] = { 0x1c000, 0x1000, 32 | 16 | 8 }, /* GPIO3 */ + [ 19] = { 0x1e000, 0x1000, 32 | 16 | 8 }, /* GPIO4 */ + [ 15] = { 0x19000, 0x1000, 32 | 16 | 8 }, /* Quad GPIO TOP */ + [ 17] = { 0x1b000, 0x1000, 32 | 16 | 8 }, /* L4TA3 */ + [ 20] = { 0x20000, 0x1000, 32 | 16 | 8 }, /* WD Timer 1 (Secure) */ + [ 22] = { 0x22000, 0x1000, 32 | 16 | 8 }, /* WD Timer 2 (OMAP) */ + [ 21] = { 0x21000, 0x1000, 32 | 16 | 8 }, /* Dual WD timer TOP */ + [ 23] = { 0x23000, 0x1000, 32 | 16 | 8 }, /* L4TA4 */ + [ 24] = { 0x28000, 0x1000, 32 | 16 | 8 }, /* GP Timer 1 */ + [ 25] = { 0x29000, 0x1000, 32 | 16 | 8 }, /* L4TA7 */ + [ 26] = { 0x48000, 0x2000, 32 | 16 | 8 }, /* Emulation (ARM11ETB) */ + [ 27] = { 0x4a000, 0x1000, 32 | 16 | 8 }, /* L4TA9 */ + [ 28] = { 0x50000, 0x400, 32 | 16 | 8 }, /* Display top */ + [ 29] = { 0x50400, 0x400, 32 | 16 | 8 }, /* Display control */ + [ 30] = { 0x50800, 0x400, 32 | 16 | 8 }, /* Display RFBI */ + [ 31] = { 0x50c00, 0x400, 32 | 16 | 8 }, /* Display encoder */ + [ 32] = { 0x51000, 0x1000, 32 | 16 | 8 }, /* L4TA10 */ + [ 33] = { 0x52000, 0x400, 32 | 16 | 8 }, /* Camera top */ + [ 34] = { 0x52400, 0x400, 32 | 16 | 8 }, /* Camera core */ + [ 35] = { 0x52800, 0x400, 32 | 16 | 8 }, /* Camera DMA */ + [ 36] = { 0x52c00, 0x400, 32 | 16 | 8 }, /* Camera MMU */ + [ 37] = { 0x53000, 0x1000, 32 | 16 | 8 }, /* L4TA11 */ + [ 38] = { 0x56000, 0x1000, 32 | 16 | 8 }, /* sDMA */ + [ 39] = { 0x57000, 0x1000, 32 | 16 | 8 }, /* L4TA12 */ + [ 40] = { 0x58000, 0x1000, 32 | 16 | 8 }, /* SSI top */ + [ 41] = { 0x59000, 0x1000, 32 | 16 | 8 }, /* SSI GDD */ + [ 42] = { 0x5a000, 0x1000, 32 | 16 | 8 }, /* SSI Port1 */ + [ 43] = { 0x5b000, 0x1000, 32 | 16 | 8 }, /* SSI Port2 */ + [ 44] = { 0x5c000, 0x1000, 32 | 16 | 8 }, /* L4TA13 */ + [ 45] = { 0x5e000, 0x1000, 32 | 16 | 8 }, /* USB OTG */ + [ 46] = { 0x5f000, 0x1000, 32 | 16 | 8 }, /* L4TAO4 */ + [ 47] = { 0x60000, 0x1000, 32 | 16 | 8 }, /* Emulation (WIN_TRACER1SDRC) */ + [ 48] = { 0x61000, 0x1000, 32 | 16 | 8 }, /* L4TA14 */ + [ 49] = { 0x62000, 0x1000, 32 | 16 | 8 }, /* Emulation (WIN_TRACER2GPMC) */ + [ 50] = { 0x63000, 0x1000, 32 | 16 | 8 }, /* L4TA15 */ + [ 51] = { 0x64000, 0x1000, 32 | 16 | 8 }, /* Emulation (WIN_TRACER3OCM) */ + [ 52] = { 0x65000, 0x1000, 32 | 16 | 8 }, /* L4TA16 */ + [ 53] = { 0x66000, 0x300, 32 | 16 | 8 }, /* Emulation (WIN_TRACER4L4) */ + [ 54] = { 0x67000, 0x1000, 32 | 16 | 8 }, /* L4TA17 */ + [ 55] = { 0x68000, 0x1000, 32 | 16 | 8 }, /* Emulation (XTI) */ + [ 56] = { 0x69000, 0x1000, 32 | 16 | 8 }, /* L4TA18 */ + [ 57] = { 0x6a000, 0x1000, 16 | 8 }, /* UART1 */ + [ 58] = { 0x6b000, 0x1000, 32 | 16 | 8 }, /* L4TA19 */ + [ 59] = { 0x6c000, 0x1000, 16 | 8 }, /* UART2 */ + [ 60] = { 0x6d000, 0x1000, 32 | 16 | 8 }, /* L4TA20 */ + [ 61] = { 0x6e000, 0x1000, 16 | 8 }, /* UART3 */ + [ 62] = { 0x6f000, 0x1000, 32 | 16 | 8 }, /* L4TA21 */ + [ 63] = { 0x70000, 0x1000, 16 }, /* I2C1 */ + [ 64] = { 0x71000, 0x1000, 32 | 16 | 8 }, /* L4TAO5 */ + [ 65] = { 0x72000, 0x1000, 16 }, /* I2C2 */ + [ 66] = { 0x73000, 0x1000, 32 | 16 | 8 }, /* L4TAO6 */ + [ 67] = { 0x74000, 0x1000, 16 }, /* McBSP1 */ + [ 68] = { 0x75000, 0x1000, 32 | 16 | 8 }, /* L4TAO7 */ + [ 69] = { 0x76000, 0x1000, 16 }, /* McBSP2 */ + [ 70] = { 0x77000, 0x1000, 32 | 16 | 8 }, /* L4TAO8 */ + [ 71] = { 0x24000, 0x1000, 32 | 16 | 8 }, /* WD Timer 3 (DSP) */ + [ 72] = { 0x25000, 0x1000, 32 | 16 | 8 }, /* L4TA5 */ + [ 73] = { 0x26000, 0x1000, 32 | 16 | 8 }, /* WD Timer 4 (IVA) */ + [ 74] = { 0x27000, 0x1000, 32 | 16 | 8 }, /* L4TA6 */ + [ 75] = { 0x2a000, 0x1000, 32 | 16 | 8 }, /* GP Timer 2 */ + [ 76] = { 0x2b000, 0x1000, 32 | 16 | 8 }, /* L4TA8 */ + [ 77] = { 0x78000, 0x1000, 32 | 16 | 8 }, /* GP Timer 3 */ + [ 78] = { 0x79000, 0x1000, 32 | 16 | 8 }, /* L4TA22 */ + [ 79] = { 0x7a000, 0x1000, 32 | 16 | 8 }, /* GP Timer 4 */ + [ 80] = { 0x7b000, 0x1000, 32 | 16 | 8 }, /* L4TA23 */ + [ 81] = { 0x7c000, 0x1000, 32 | 16 | 8 }, /* GP Timer 5 */ + [ 82] = { 0x7d000, 0x1000, 32 | 16 | 8 }, /* L4TA24 */ + [ 83] = { 0x7e000, 0x1000, 32 | 16 | 8 }, /* GP Timer 6 */ + [ 84] = { 0x7f000, 0x1000, 32 | 16 | 8 }, /* L4TA25 */ + [ 85] = { 0x80000, 0x1000, 32 | 16 | 8 }, /* GP Timer 7 */ + [ 86] = { 0x81000, 0x1000, 32 | 16 | 8 }, /* L4TA26 */ + [ 87] = { 0x82000, 0x1000, 32 | 16 | 8 }, /* GP Timer 8 */ + [ 88] = { 0x83000, 0x1000, 32 | 16 | 8 }, /* L4TA27 */ + [ 89] = { 0x84000, 0x1000, 32 | 16 | 8 }, /* GP Timer 9 */ + [ 90] = { 0x85000, 0x1000, 32 | 16 | 8 }, /* L4TA28 */ + [ 91] = { 0x86000, 0x1000, 32 | 16 | 8 }, /* GP Timer 10 */ + [ 92] = { 0x87000, 0x1000, 32 | 16 | 8 }, /* L4TA29 */ + [ 93] = { 0x88000, 0x1000, 32 | 16 | 8 }, /* GP Timer 11 */ + [ 94] = { 0x89000, 0x1000, 32 | 16 | 8 }, /* L4TA30 */ + [ 95] = { 0x8a000, 0x1000, 32 | 16 | 8 }, /* GP Timer 12 */ + [ 96] = { 0x8b000, 0x1000, 32 | 16 | 8 }, /* L4TA31 */ + [ 97] = { 0x90000, 0x1000, 16 }, /* EAC */ + [ 98] = { 0x91000, 0x1000, 32 | 16 | 8 }, /* L4TA32 */ + [ 99] = { 0x92000, 0x1000, 16 }, /* FAC */ + [100] = { 0x93000, 0x1000, 32 | 16 | 8 }, /* L4TA33 */ + [101] = { 0x94000, 0x1000, 32 | 16 | 8 }, /* IPC (MAILBOX) */ + [102] = { 0x95000, 0x1000, 32 | 16 | 8 }, /* L4TA34 */ + [103] = { 0x98000, 0x1000, 32 | 16 | 8 }, /* SPI1 */ + [104] = { 0x99000, 0x1000, 32 | 16 | 8 }, /* L4TA35 */ + [105] = { 0x9a000, 0x1000, 32 | 16 | 8 }, /* SPI2 */ + [106] = { 0x9b000, 0x1000, 32 | 16 | 8 }, /* L4TA36 */ + [107] = { 0x9c000, 0x1000, 16 | 8 }, /* MMC SDIO */ + [108] = { 0x9d000, 0x1000, 32 | 16 | 8 }, /* L4TAO9 */ + [109] = { 0x9e000, 0x1000, 32 | 16 | 8 }, /* MS_PRO */ + [110] = { 0x9f000, 0x1000, 32 | 16 | 8 }, /* L4TAO10 */ + [111] = { 0xa0000, 0x1000, 32 }, /* RNG */ + [112] = { 0xa1000, 0x1000, 32 | 16 | 8 }, /* L4TAO11 */ + [113] = { 0xa2000, 0x1000, 32 }, /* DES3DES */ + [114] = { 0xa3000, 0x1000, 32 | 16 | 8 }, /* L4TAO12 */ + [115] = { 0xa4000, 0x1000, 32 }, /* SHA1MD5 */ + [116] = { 0xa5000, 0x1000, 32 | 16 | 8 }, /* L4TAO13 */ + [117] = { 0xa6000, 0x1000, 32 }, /* AES */ + [118] = { 0xa7000, 0x1000, 32 | 16 | 8 }, /* L4TA37 */ + [119] = { 0xa8000, 0x2000, 32 }, /* PKA */ + [120] = { 0xaa000, 0x1000, 32 | 16 | 8 }, /* L4TA38 */ + [121] = { 0xb0000, 0x1000, 32 }, /* MG */ + [122] = { 0xb1000, 0x1000, 32 | 16 | 8 }, + [123] = { 0xb2000, 0x1000, 32 }, /* HDQ/1-Wire */ + [124] = { 0xb3000, 0x1000, 32 | 16 | 8 }, /* L4TA39 */ +}; + +static const struct omap_l4_agent_info_s omap_l4_agent_info[54] = { + { 0, 0, 3, 2 }, /* L4IA initiatior agent */ + { L4TAO(1), 3, 2, 1 }, /* Control and pinout module */ + { L4TAO(2), 5, 2, 1 }, /* 32K timer */ + { L4TAO(3), 7, 3, 2 }, /* PRCM */ + { L4TA(1), 10, 2, 1 }, /* BCM */ + { L4TA(2), 12, 2, 1 }, /* Test JTAG */ + { L4TA(3), 14, 6, 3 }, /* Quad GPIO */ + { L4TA(4), 20, 4, 3 }, /* WD timer 1/2 */ + { L4TA(7), 24, 2, 1 }, /* GP timer 1 */ + { L4TA(9), 26, 2, 1 }, /* ATM11 ETB */ + { L4TA(10), 28, 5, 4 }, /* Display subsystem */ + { L4TA(11), 33, 5, 4 }, /* Camera subsystem */ + { L4TA(12), 38, 2, 1 }, /* sDMA */ + { L4TA(13), 40, 5, 4 }, /* SSI */ + { L4TAO(4), 45, 2, 1 }, /* USB */ + { L4TA(14), 47, 2, 1 }, /* Win Tracer1 */ + { L4TA(15), 49, 2, 1 }, /* Win Tracer2 */ + { L4TA(16), 51, 2, 1 }, /* Win Tracer3 */ + { L4TA(17), 53, 2, 1 }, /* Win Tracer4 */ + { L4TA(18), 55, 2, 1 }, /* XTI */ + { L4TA(19), 57, 2, 1 }, /* UART1 */ + { L4TA(20), 59, 2, 1 }, /* UART2 */ + { L4TA(21), 61, 2, 1 }, /* UART3 */ + { L4TAO(5), 63, 2, 1 }, /* I2C1 */ + { L4TAO(6), 65, 2, 1 }, /* I2C2 */ + { L4TAO(7), 67, 2, 1 }, /* McBSP1 */ + { L4TAO(8), 69, 2, 1 }, /* McBSP2 */ + { L4TA(5), 71, 2, 1 }, /* WD Timer 3 (DSP) */ + { L4TA(6), 73, 2, 1 }, /* WD Timer 4 (IVA) */ + { L4TA(8), 75, 2, 1 }, /* GP Timer 2 */ + { L4TA(22), 77, 2, 1 }, /* GP Timer 3 */ + { L4TA(23), 79, 2, 1 }, /* GP Timer 4 */ + { L4TA(24), 81, 2, 1 }, /* GP Timer 5 */ + { L4TA(25), 83, 2, 1 }, /* GP Timer 6 */ + { L4TA(26), 85, 2, 1 }, /* GP Timer 7 */ + { L4TA(27), 87, 2, 1 }, /* GP Timer 8 */ + { L4TA(28), 89, 2, 1 }, /* GP Timer 9 */ + { L4TA(29), 91, 2, 1 }, /* GP Timer 10 */ + { L4TA(30), 93, 2, 1 }, /* GP Timer 11 */ + { L4TA(31), 95, 2, 1 }, /* GP Timer 12 */ + { L4TA(32), 97, 2, 1 }, /* EAC */ + { L4TA(33), 99, 2, 1 }, /* FAC */ + { L4TA(34), 101, 2, 1 }, /* IPC */ + { L4TA(35), 103, 2, 1 }, /* SPI1 */ + { L4TA(36), 105, 2, 1 }, /* SPI2 */ + { L4TAO(9), 107, 2, 1 }, /* MMC SDIO */ + { L4TAO(10), 109, 2, 1 }, + { L4TAO(11), 111, 2, 1 }, /* RNG */ + { L4TAO(12), 113, 2, 1 }, /* DES3DES */ + { L4TAO(13), 115, 2, 1 }, /* SHA1MD5 */ + { L4TA(37), 117, 2, 1 }, /* AES */ + { L4TA(38), 119, 2, 1 }, /* PKA */ + { -1, 121, 2, 1 }, + { L4TA(39), 123, 2, 1 }, /* HDQ/1-Wire */ +}; + +#define omap_l4ta(bus, cs) \ + omap_l4ta_get(bus, omap_l4_region, omap_l4_agent_info, L4TA(cs)) +#define omap_l4tao(bus, cs) \ + omap_l4ta_get(bus, omap_l4_region, omap_l4_agent_info, L4TAO(cs)) + +/* Power, Reset, and Clock Management */ +struct omap_prcm_s { + qemu_irq irq[3]; + struct omap_mpu_state_s *mpu; + MemoryRegion iomem0; + MemoryRegion iomem1; + + uint32_t irqst[3]; + uint32_t irqen[3]; + + uint32_t sysconfig; + uint32_t voltctrl; + uint32_t scratch[20]; + + uint32_t clksrc[1]; + uint32_t clkout[1]; + uint32_t clkemul[1]; + uint32_t clkpol[1]; + uint32_t clksel[8]; + uint32_t clken[12]; + uint32_t clkctrl[4]; + uint32_t clkidle[7]; + uint32_t setuptime[2]; + + uint32_t wkup[3]; + uint32_t wken[3]; + uint32_t wkst[3]; + uint32_t rst[4]; + uint32_t rstctrl[1]; + uint32_t power[4]; + uint32_t rsttime_wkup; + + uint32_t ev; + uint32_t evtime[2]; + + int dpll_lock, apll_lock[2]; +}; + +static void omap_prcm_int_update(struct omap_prcm_s *s, int dom) +{ + qemu_set_irq(s->irq[dom], s->irqst[dom] & s->irqen[dom]); + /* XXX or is the mask applied before PRCM_IRQSTATUS_* ? */ +} + +static uint64_t omap_prcm_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_prcm_s *s = (struct omap_prcm_s *) opaque; + uint32_t ret; + + if (size != 4) { + return omap_badwidth_read32(opaque, addr); + } + + switch (addr) { + case 0x000: /* PRCM_REVISION */ + return 0x10; + + case 0x010: /* PRCM_SYSCONFIG */ + return s->sysconfig; + + case 0x018: /* PRCM_IRQSTATUS_MPU */ + return s->irqst[0]; + + case 0x01c: /* PRCM_IRQENABLE_MPU */ + return s->irqen[0]; + + case 0x050: /* PRCM_VOLTCTRL */ + return s->voltctrl; + case 0x054: /* PRCM_VOLTST */ + return s->voltctrl & 3; + + case 0x060: /* PRCM_CLKSRC_CTRL */ + return s->clksrc[0]; + case 0x070: /* PRCM_CLKOUT_CTRL */ + return s->clkout[0]; + case 0x078: /* PRCM_CLKEMUL_CTRL */ + return s->clkemul[0]; + case 0x080: /* PRCM_CLKCFG_CTRL */ + case 0x084: /* PRCM_CLKCFG_STATUS */ + return 0; + + case 0x090: /* PRCM_VOLTSETUP */ + return s->setuptime[0]; + + case 0x094: /* PRCM_CLKSSETUP */ + return s->setuptime[1]; + + case 0x098: /* PRCM_POLCTRL */ + return s->clkpol[0]; + + case 0x0b0: /* GENERAL_PURPOSE1 */ + case 0x0b4: /* GENERAL_PURPOSE2 */ + case 0x0b8: /* GENERAL_PURPOSE3 */ + case 0x0bc: /* GENERAL_PURPOSE4 */ + case 0x0c0: /* GENERAL_PURPOSE5 */ + case 0x0c4: /* GENERAL_PURPOSE6 */ + case 0x0c8: /* GENERAL_PURPOSE7 */ + case 0x0cc: /* GENERAL_PURPOSE8 */ + case 0x0d0: /* GENERAL_PURPOSE9 */ + case 0x0d4: /* GENERAL_PURPOSE10 */ + case 0x0d8: /* GENERAL_PURPOSE11 */ + case 0x0dc: /* GENERAL_PURPOSE12 */ + case 0x0e0: /* GENERAL_PURPOSE13 */ + case 0x0e4: /* GENERAL_PURPOSE14 */ + case 0x0e8: /* GENERAL_PURPOSE15 */ + case 0x0ec: /* GENERAL_PURPOSE16 */ + case 0x0f0: /* GENERAL_PURPOSE17 */ + case 0x0f4: /* GENERAL_PURPOSE18 */ + case 0x0f8: /* GENERAL_PURPOSE19 */ + case 0x0fc: /* GENERAL_PURPOSE20 */ + return s->scratch[(addr - 0xb0) >> 2]; + + case 0x140: /* CM_CLKSEL_MPU */ + return s->clksel[0]; + case 0x148: /* CM_CLKSTCTRL_MPU */ + return s->clkctrl[0]; + + case 0x158: /* RM_RSTST_MPU */ + return s->rst[0]; + case 0x1c8: /* PM_WKDEP_MPU */ + return s->wkup[0]; + case 0x1d4: /* PM_EVGENCTRL_MPU */ + return s->ev; + case 0x1d8: /* PM_EVEGENONTIM_MPU */ + return s->evtime[0]; + case 0x1dc: /* PM_EVEGENOFFTIM_MPU */ + return s->evtime[1]; + case 0x1e0: /* PM_PWSTCTRL_MPU */ + return s->power[0]; + case 0x1e4: /* PM_PWSTST_MPU */ + return 0; + + case 0x200: /* CM_FCLKEN1_CORE */ + return s->clken[0]; + case 0x204: /* CM_FCLKEN2_CORE */ + return s->clken[1]; + case 0x210: /* CM_ICLKEN1_CORE */ + return s->clken[2]; + case 0x214: /* CM_ICLKEN2_CORE */ + return s->clken[3]; + case 0x21c: /* CM_ICLKEN4_CORE */ + return s->clken[4]; + + case 0x220: /* CM_IDLEST1_CORE */ + /* TODO: check the actual iclk status */ + return 0x7ffffff9; + case 0x224: /* CM_IDLEST2_CORE */ + /* TODO: check the actual iclk status */ + return 0x00000007; + case 0x22c: /* CM_IDLEST4_CORE */ + /* TODO: check the actual iclk status */ + return 0x0000001f; + + case 0x230: /* CM_AUTOIDLE1_CORE */ + return s->clkidle[0]; + case 0x234: /* CM_AUTOIDLE2_CORE */ + return s->clkidle[1]; + case 0x238: /* CM_AUTOIDLE3_CORE */ + return s->clkidle[2]; + case 0x23c: /* CM_AUTOIDLE4_CORE */ + return s->clkidle[3]; + + case 0x240: /* CM_CLKSEL1_CORE */ + return s->clksel[1]; + case 0x244: /* CM_CLKSEL2_CORE */ + return s->clksel[2]; + + case 0x248: /* CM_CLKSTCTRL_CORE */ + return s->clkctrl[1]; + + case 0x2a0: /* PM_WKEN1_CORE */ + return s->wken[0]; + case 0x2a4: /* PM_WKEN2_CORE */ + return s->wken[1]; + + case 0x2b0: /* PM_WKST1_CORE */ + return s->wkst[0]; + case 0x2b4: /* PM_WKST2_CORE */ + return s->wkst[1]; + case 0x2c8: /* PM_WKDEP_CORE */ + return 0x1e; + + case 0x2e0: /* PM_PWSTCTRL_CORE */ + return s->power[1]; + case 0x2e4: /* PM_PWSTST_CORE */ + return 0x000030 | (s->power[1] & 0xfc00); + + case 0x300: /* CM_FCLKEN_GFX */ + return s->clken[5]; + case 0x310: /* CM_ICLKEN_GFX */ + return s->clken[6]; + case 0x320: /* CM_IDLEST_GFX */ + /* TODO: check the actual iclk status */ + return 0x00000001; + case 0x340: /* CM_CLKSEL_GFX */ + return s->clksel[3]; + case 0x348: /* CM_CLKSTCTRL_GFX */ + return s->clkctrl[2]; + case 0x350: /* RM_RSTCTRL_GFX */ + return s->rstctrl[0]; + case 0x358: /* RM_RSTST_GFX */ + return s->rst[1]; + case 0x3c8: /* PM_WKDEP_GFX */ + return s->wkup[1]; + + case 0x3e0: /* PM_PWSTCTRL_GFX */ + return s->power[2]; + case 0x3e4: /* PM_PWSTST_GFX */ + return s->power[2] & 3; + + case 0x400: /* CM_FCLKEN_WKUP */ + return s->clken[7]; + case 0x410: /* CM_ICLKEN_WKUP */ + return s->clken[8]; + case 0x420: /* CM_IDLEST_WKUP */ + /* TODO: check the actual iclk status */ + return 0x0000003f; + case 0x430: /* CM_AUTOIDLE_WKUP */ + return s->clkidle[4]; + case 0x440: /* CM_CLKSEL_WKUP */ + return s->clksel[4]; + case 0x450: /* RM_RSTCTRL_WKUP */ + return 0; + case 0x454: /* RM_RSTTIME_WKUP */ + return s->rsttime_wkup; + case 0x458: /* RM_RSTST_WKUP */ + return s->rst[2]; + case 0x4a0: /* PM_WKEN_WKUP */ + return s->wken[2]; + case 0x4b0: /* PM_WKST_WKUP */ + return s->wkst[2]; + + case 0x500: /* CM_CLKEN_PLL */ + return s->clken[9]; + case 0x520: /* CM_IDLEST_CKGEN */ + ret = 0x0000070 | (s->apll_lock[0] << 9) | (s->apll_lock[1] << 8); + if (!(s->clksel[6] & 3)) + /* Core uses 32-kHz clock */ + ret |= 3 << 0; + else if (!s->dpll_lock) + /* DPLL not locked, core uses ref_clk */ + ret |= 1 << 0; + else + /* Core uses DPLL */ + ret |= 2 << 0; + return ret; + case 0x530: /* CM_AUTOIDLE_PLL */ + return s->clkidle[5]; + case 0x540: /* CM_CLKSEL1_PLL */ + return s->clksel[5]; + case 0x544: /* CM_CLKSEL2_PLL */ + return s->clksel[6]; + + case 0x800: /* CM_FCLKEN_DSP */ + return s->clken[10]; + case 0x810: /* CM_ICLKEN_DSP */ + return s->clken[11]; + case 0x820: /* CM_IDLEST_DSP */ + /* TODO: check the actual iclk status */ + return 0x00000103; + case 0x830: /* CM_AUTOIDLE_DSP */ + return s->clkidle[6]; + case 0x840: /* CM_CLKSEL_DSP */ + return s->clksel[7]; + case 0x848: /* CM_CLKSTCTRL_DSP */ + return s->clkctrl[3]; + case 0x850: /* RM_RSTCTRL_DSP */ + return 0; + case 0x858: /* RM_RSTST_DSP */ + return s->rst[3]; + case 0x8c8: /* PM_WKDEP_DSP */ + return s->wkup[2]; + case 0x8e0: /* PM_PWSTCTRL_DSP */ + return s->power[3]; + case 0x8e4: /* PM_PWSTST_DSP */ + return 0x008030 | (s->power[3] & 0x3003); + + case 0x8f0: /* PRCM_IRQSTATUS_DSP */ + return s->irqst[1]; + case 0x8f4: /* PRCM_IRQENABLE_DSP */ + return s->irqen[1]; + + case 0x8f8: /* PRCM_IRQSTATUS_IVA */ + return s->irqst[2]; + case 0x8fc: /* PRCM_IRQENABLE_IVA */ + return s->irqen[2]; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_prcm_apll_update(struct omap_prcm_s *s) +{ + int mode[2]; + + mode[0] = (s->clken[9] >> 6) & 3; + s->apll_lock[0] = (mode[0] == 3); + mode[1] = (s->clken[9] >> 2) & 3; + s->apll_lock[1] = (mode[1] == 3); + /* TODO: update clocks */ + + if (mode[0] == 1 || mode[0] == 2 || mode[1] == 1 || mode[1] == 2) + fprintf(stderr, "%s: bad EN_54M_PLL or bad EN_96M_PLL\n", + __FUNCTION__); +} + +static void omap_prcm_dpll_update(struct omap_prcm_s *s) +{ + omap_clk dpll = omap_findclk(s->mpu, "dpll"); + omap_clk dpll_x2 = omap_findclk(s->mpu, "dpll"); + omap_clk core = omap_findclk(s->mpu, "core_clk"); + int mode = (s->clken[9] >> 0) & 3; + int mult, div; + + mult = (s->clksel[5] >> 12) & 0x3ff; + div = (s->clksel[5] >> 8) & 0xf; + if (mult == 0 || mult == 1) + mode = 1; /* Bypass */ + + s->dpll_lock = 0; + switch (mode) { + case 0: + fprintf(stderr, "%s: bad EN_DPLL\n", __FUNCTION__); + break; + case 1: /* Low-power bypass mode (Default) */ + case 2: /* Fast-relock bypass mode */ + omap_clk_setrate(dpll, 1, 1); + omap_clk_setrate(dpll_x2, 1, 1); + break; + case 3: /* Lock mode */ + s->dpll_lock = 1; /* After 20 FINT cycles (ref_clk / (div + 1)). */ + + omap_clk_setrate(dpll, div + 1, mult); + omap_clk_setrate(dpll_x2, div + 1, mult * 2); + break; + } + + switch ((s->clksel[6] >> 0) & 3) { + case 0: + omap_clk_reparent(core, omap_findclk(s->mpu, "clk32-kHz")); + break; + case 1: + omap_clk_reparent(core, dpll); + break; + case 2: + /* Default */ + omap_clk_reparent(core, dpll_x2); + break; + case 3: + fprintf(stderr, "%s: bad CORE_CLK_SRC\n", __FUNCTION__); + break; + } +} + +static void omap_prcm_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_prcm_s *s = (struct omap_prcm_s *) opaque; + + if (size != 4) { + omap_badwidth_write32(opaque, addr, value); + return; + } + + switch (addr) { + case 0x000: /* PRCM_REVISION */ + case 0x054: /* PRCM_VOLTST */ + case 0x084: /* PRCM_CLKCFG_STATUS */ + case 0x1e4: /* PM_PWSTST_MPU */ + case 0x220: /* CM_IDLEST1_CORE */ + case 0x224: /* CM_IDLEST2_CORE */ + case 0x22c: /* CM_IDLEST4_CORE */ + case 0x2c8: /* PM_WKDEP_CORE */ + case 0x2e4: /* PM_PWSTST_CORE */ + case 0x320: /* CM_IDLEST_GFX */ + case 0x3e4: /* PM_PWSTST_GFX */ + case 0x420: /* CM_IDLEST_WKUP */ + case 0x520: /* CM_IDLEST_CKGEN */ + case 0x820: /* CM_IDLEST_DSP */ + case 0x8e4: /* PM_PWSTST_DSP */ + OMAP_RO_REG(addr); + return; + + case 0x010: /* PRCM_SYSCONFIG */ + s->sysconfig = value & 1; + break; + + case 0x018: /* PRCM_IRQSTATUS_MPU */ + s->irqst[0] &= ~value; + omap_prcm_int_update(s, 0); + break; + case 0x01c: /* PRCM_IRQENABLE_MPU */ + s->irqen[0] = value & 0x3f; + omap_prcm_int_update(s, 0); + break; + + case 0x050: /* PRCM_VOLTCTRL */ + s->voltctrl = value & 0xf1c3; + break; + + case 0x060: /* PRCM_CLKSRC_CTRL */ + s->clksrc[0] = value & 0xdb; + /* TODO update clocks */ + break; + + case 0x070: /* PRCM_CLKOUT_CTRL */ + s->clkout[0] = value & 0xbbbb; + /* TODO update clocks */ + break; + + case 0x078: /* PRCM_CLKEMUL_CTRL */ + s->clkemul[0] = value & 1; + /* TODO update clocks */ + break; + + case 0x080: /* PRCM_CLKCFG_CTRL */ + break; + + case 0x090: /* PRCM_VOLTSETUP */ + s->setuptime[0] = value & 0xffff; + break; + case 0x094: /* PRCM_CLKSSETUP */ + s->setuptime[1] = value & 0xffff; + break; + + case 0x098: /* PRCM_POLCTRL */ + s->clkpol[0] = value & 0x701; + break; + + case 0x0b0: /* GENERAL_PURPOSE1 */ + case 0x0b4: /* GENERAL_PURPOSE2 */ + case 0x0b8: /* GENERAL_PURPOSE3 */ + case 0x0bc: /* GENERAL_PURPOSE4 */ + case 0x0c0: /* GENERAL_PURPOSE5 */ + case 0x0c4: /* GENERAL_PURPOSE6 */ + case 0x0c8: /* GENERAL_PURPOSE7 */ + case 0x0cc: /* GENERAL_PURPOSE8 */ + case 0x0d0: /* GENERAL_PURPOSE9 */ + case 0x0d4: /* GENERAL_PURPOSE10 */ + case 0x0d8: /* GENERAL_PURPOSE11 */ + case 0x0dc: /* GENERAL_PURPOSE12 */ + case 0x0e0: /* GENERAL_PURPOSE13 */ + case 0x0e4: /* GENERAL_PURPOSE14 */ + case 0x0e8: /* GENERAL_PURPOSE15 */ + case 0x0ec: /* GENERAL_PURPOSE16 */ + case 0x0f0: /* GENERAL_PURPOSE17 */ + case 0x0f4: /* GENERAL_PURPOSE18 */ + case 0x0f8: /* GENERAL_PURPOSE19 */ + case 0x0fc: /* GENERAL_PURPOSE20 */ + s->scratch[(addr - 0xb0) >> 2] = value; + break; + + case 0x140: /* CM_CLKSEL_MPU */ + s->clksel[0] = value & 0x1f; + /* TODO update clocks */ + break; + case 0x148: /* CM_CLKSTCTRL_MPU */ + s->clkctrl[0] = value & 0x1f; + break; + + case 0x158: /* RM_RSTST_MPU */ + s->rst[0] &= ~value; + break; + case 0x1c8: /* PM_WKDEP_MPU */ + s->wkup[0] = value & 0x15; + break; + + case 0x1d4: /* PM_EVGENCTRL_MPU */ + s->ev = value & 0x1f; + break; + case 0x1d8: /* PM_EVEGENONTIM_MPU */ + s->evtime[0] = value; + break; + case 0x1dc: /* PM_EVEGENOFFTIM_MPU */ + s->evtime[1] = value; + break; + + case 0x1e0: /* PM_PWSTCTRL_MPU */ + s->power[0] = value & 0xc0f; + break; + + case 0x200: /* CM_FCLKEN1_CORE */ + s->clken[0] = value & 0xbfffffff; + /* TODO update clocks */ + /* The EN_EAC bit only gets/puts func_96m_clk. */ + break; + case 0x204: /* CM_FCLKEN2_CORE */ + s->clken[1] = value & 0x00000007; + /* TODO update clocks */ + break; + case 0x210: /* CM_ICLKEN1_CORE */ + s->clken[2] = value & 0xfffffff9; + /* TODO update clocks */ + /* The EN_EAC bit only gets/puts core_l4_iclk. */ + break; + case 0x214: /* CM_ICLKEN2_CORE */ + s->clken[3] = value & 0x00000007; + /* TODO update clocks */ + break; + case 0x21c: /* CM_ICLKEN4_CORE */ + s->clken[4] = value & 0x0000001f; + /* TODO update clocks */ + break; + + case 0x230: /* CM_AUTOIDLE1_CORE */ + s->clkidle[0] = value & 0xfffffff9; + /* TODO update clocks */ + break; + case 0x234: /* CM_AUTOIDLE2_CORE */ + s->clkidle[1] = value & 0x00000007; + /* TODO update clocks */ + break; + case 0x238: /* CM_AUTOIDLE3_CORE */ + s->clkidle[2] = value & 0x00000007; + /* TODO update clocks */ + break; + case 0x23c: /* CM_AUTOIDLE4_CORE */ + s->clkidle[3] = value & 0x0000001f; + /* TODO update clocks */ + break; + + case 0x240: /* CM_CLKSEL1_CORE */ + s->clksel[1] = value & 0x0fffbf7f; + /* TODO update clocks */ + break; + + case 0x244: /* CM_CLKSEL2_CORE */ + s->clksel[2] = value & 0x00fffffc; + /* TODO update clocks */ + break; + + case 0x248: /* CM_CLKSTCTRL_CORE */ + s->clkctrl[1] = value & 0x7; + break; + + case 0x2a0: /* PM_WKEN1_CORE */ + s->wken[0] = value & 0x04667ff8; + break; + case 0x2a4: /* PM_WKEN2_CORE */ + s->wken[1] = value & 0x00000005; + break; + + case 0x2b0: /* PM_WKST1_CORE */ + s->wkst[0] &= ~value; + break; + case 0x2b4: /* PM_WKST2_CORE */ + s->wkst[1] &= ~value; + break; + + case 0x2e0: /* PM_PWSTCTRL_CORE */ + s->power[1] = (value & 0x00fc3f) | (1 << 2); + break; + + case 0x300: /* CM_FCLKEN_GFX */ + s->clken[5] = value & 6; + /* TODO update clocks */ + break; + case 0x310: /* CM_ICLKEN_GFX */ + s->clken[6] = value & 1; + /* TODO update clocks */ + break; + case 0x340: /* CM_CLKSEL_GFX */ + s->clksel[3] = value & 7; + /* TODO update clocks */ + break; + case 0x348: /* CM_CLKSTCTRL_GFX */ + s->clkctrl[2] = value & 1; + break; + case 0x350: /* RM_RSTCTRL_GFX */ + s->rstctrl[0] = value & 1; + /* TODO: reset */ + break; + case 0x358: /* RM_RSTST_GFX */ + s->rst[1] &= ~value; + break; + case 0x3c8: /* PM_WKDEP_GFX */ + s->wkup[1] = value & 0x13; + break; + case 0x3e0: /* PM_PWSTCTRL_GFX */ + s->power[2] = (value & 0x00c0f) | (3 << 2); + break; + + case 0x400: /* CM_FCLKEN_WKUP */ + s->clken[7] = value & 0xd; + /* TODO update clocks */ + break; + case 0x410: /* CM_ICLKEN_WKUP */ + s->clken[8] = value & 0x3f; + /* TODO update clocks */ + break; + case 0x430: /* CM_AUTOIDLE_WKUP */ + s->clkidle[4] = value & 0x0000003f; + /* TODO update clocks */ + break; + case 0x440: /* CM_CLKSEL_WKUP */ + s->clksel[4] = value & 3; + /* TODO update clocks */ + break; + case 0x450: /* RM_RSTCTRL_WKUP */ + /* TODO: reset */ + if (value & 2) + qemu_system_reset_request(); + break; + case 0x454: /* RM_RSTTIME_WKUP */ + s->rsttime_wkup = value & 0x1fff; + break; + case 0x458: /* RM_RSTST_WKUP */ + s->rst[2] &= ~value; + break; + case 0x4a0: /* PM_WKEN_WKUP */ + s->wken[2] = value & 0x00000005; + break; + case 0x4b0: /* PM_WKST_WKUP */ + s->wkst[2] &= ~value; + break; + + case 0x500: /* CM_CLKEN_PLL */ + if (value & 0xffffff30) + fprintf(stderr, "%s: write 0s in CM_CLKEN_PLL for " + "future compatibility\n", __FUNCTION__); + if ((s->clken[9] ^ value) & 0xcc) { + s->clken[9] &= ~0xcc; + s->clken[9] |= value & 0xcc; + omap_prcm_apll_update(s); + } + if ((s->clken[9] ^ value) & 3) { + s->clken[9] &= ~3; + s->clken[9] |= value & 3; + omap_prcm_dpll_update(s); + } + break; + case 0x530: /* CM_AUTOIDLE_PLL */ + s->clkidle[5] = value & 0x000000cf; + /* TODO update clocks */ + break; + case 0x540: /* CM_CLKSEL1_PLL */ + if (value & 0xfc4000d7) + fprintf(stderr, "%s: write 0s in CM_CLKSEL1_PLL for " + "future compatibility\n", __FUNCTION__); + if ((s->clksel[5] ^ value) & 0x003fff00) { + s->clksel[5] = value & 0x03bfff28; + omap_prcm_dpll_update(s); + } + /* TODO update the other clocks */ + + s->clksel[5] = value & 0x03bfff28; + break; + case 0x544: /* CM_CLKSEL2_PLL */ + if (value & ~3) + fprintf(stderr, "%s: write 0s in CM_CLKSEL2_PLL[31:2] for " + "future compatibility\n", __FUNCTION__); + if (s->clksel[6] != (value & 3)) { + s->clksel[6] = value & 3; + omap_prcm_dpll_update(s); + } + break; + + case 0x800: /* CM_FCLKEN_DSP */ + s->clken[10] = value & 0x501; + /* TODO update clocks */ + break; + case 0x810: /* CM_ICLKEN_DSP */ + s->clken[11] = value & 0x2; + /* TODO update clocks */ + break; + case 0x830: /* CM_AUTOIDLE_DSP */ + s->clkidle[6] = value & 0x2; + /* TODO update clocks */ + break; + case 0x840: /* CM_CLKSEL_DSP */ + s->clksel[7] = value & 0x3fff; + /* TODO update clocks */ + break; + case 0x848: /* CM_CLKSTCTRL_DSP */ + s->clkctrl[3] = value & 0x101; + break; + case 0x850: /* RM_RSTCTRL_DSP */ + /* TODO: reset */ + break; + case 0x858: /* RM_RSTST_DSP */ + s->rst[3] &= ~value; + break; + case 0x8c8: /* PM_WKDEP_DSP */ + s->wkup[2] = value & 0x13; + break; + case 0x8e0: /* PM_PWSTCTRL_DSP */ + s->power[3] = (value & 0x03017) | (3 << 2); + break; + + case 0x8f0: /* PRCM_IRQSTATUS_DSP */ + s->irqst[1] &= ~value; + omap_prcm_int_update(s, 1); + break; + case 0x8f4: /* PRCM_IRQENABLE_DSP */ + s->irqen[1] = value & 0x7; + omap_prcm_int_update(s, 1); + break; + + case 0x8f8: /* PRCM_IRQSTATUS_IVA */ + s->irqst[2] &= ~value; + omap_prcm_int_update(s, 2); + break; + case 0x8fc: /* PRCM_IRQENABLE_IVA */ + s->irqen[2] = value & 0x7; + omap_prcm_int_update(s, 2); + break; + + default: + OMAP_BAD_REG(addr); + return; + } +} + +static const MemoryRegionOps omap_prcm_ops = { + .read = omap_prcm_read, + .write = omap_prcm_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_prcm_reset(struct omap_prcm_s *s) +{ + s->sysconfig = 0; + s->irqst[0] = 0; + s->irqst[1] = 0; + s->irqst[2] = 0; + s->irqen[0] = 0; + s->irqen[1] = 0; + s->irqen[2] = 0; + s->voltctrl = 0x1040; + s->ev = 0x14; + s->evtime[0] = 0; + s->evtime[1] = 0; + s->clkctrl[0] = 0; + s->clkctrl[1] = 0; + s->clkctrl[2] = 0; + s->clkctrl[3] = 0; + s->clken[1] = 7; + s->clken[3] = 7; + s->clken[4] = 0; + s->clken[5] = 0; + s->clken[6] = 0; + s->clken[7] = 0xc; + s->clken[8] = 0x3e; + s->clken[9] = 0x0d; + s->clken[10] = 0; + s->clken[11] = 0; + s->clkidle[0] = 0; + s->clkidle[2] = 7; + s->clkidle[3] = 0; + s->clkidle[4] = 0; + s->clkidle[5] = 0x0c; + s->clkidle[6] = 0; + s->clksel[0] = 0x01; + s->clksel[1] = 0x02100121; + s->clksel[2] = 0x00000000; + s->clksel[3] = 0x01; + s->clksel[4] = 0; + s->clksel[7] = 0x0121; + s->wkup[0] = 0x15; + s->wkup[1] = 0x13; + s->wkup[2] = 0x13; + s->wken[0] = 0x04667ff8; + s->wken[1] = 0x00000005; + s->wken[2] = 5; + s->wkst[0] = 0; + s->wkst[1] = 0; + s->wkst[2] = 0; + s->power[0] = 0x00c; + s->power[1] = 4; + s->power[2] = 0x0000c; + s->power[3] = 0x14; + s->rstctrl[0] = 1; + s->rst[3] = 1; + omap_prcm_apll_update(s); + omap_prcm_dpll_update(s); +} + +static void omap_prcm_coldreset(struct omap_prcm_s *s) +{ + s->setuptime[0] = 0; + s->setuptime[1] = 0; + memset(&s->scratch, 0, sizeof(s->scratch)); + s->rst[0] = 0x01; + s->rst[1] = 0x00; + s->rst[2] = 0x01; + s->clken[0] = 0; + s->clken[2] = 0; + s->clkidle[1] = 0; + s->clksel[5] = 0; + s->clksel[6] = 2; + s->clksrc[0] = 0x43; + s->clkout[0] = 0x0303; + s->clkemul[0] = 0; + s->clkpol[0] = 0x100; + s->rsttime_wkup = 0x1002; + + omap_prcm_reset(s); +} + +static struct omap_prcm_s *omap_prcm_init(struct omap_target_agent_s *ta, + qemu_irq mpu_int, qemu_irq dsp_int, qemu_irq iva_int, + struct omap_mpu_state_s *mpu) +{ + struct omap_prcm_s *s = (struct omap_prcm_s *) + g_malloc0(sizeof(struct omap_prcm_s)); + + s->irq[0] = mpu_int; + s->irq[1] = dsp_int; + s->irq[2] = iva_int; + s->mpu = mpu; + omap_prcm_coldreset(s); + + memory_region_init_io(&s->iomem0, NULL, &omap_prcm_ops, s, "omap.pcrm0", + omap_l4_region_size(ta, 0)); + memory_region_init_io(&s->iomem1, NULL, &omap_prcm_ops, s, "omap.pcrm1", + omap_l4_region_size(ta, 1)); + omap_l4_attach(ta, 0, &s->iomem0); + omap_l4_attach(ta, 1, &s->iomem1); + + return s; +} + +/* System and Pinout control */ +struct omap_sysctl_s { + struct omap_mpu_state_s *mpu; + MemoryRegion iomem; + + uint32_t sysconfig; + uint32_t devconfig; + uint32_t psaconfig; + uint32_t padconf[0x45]; + uint8_t obs; + uint32_t msuspendmux[5]; +}; + +static uint32_t omap_sysctl_read8(void *opaque, hwaddr addr) +{ + + struct omap_sysctl_s *s = (struct omap_sysctl_s *) opaque; + int pad_offset, byte_offset; + int value; + + switch (addr) { + case 0x030 ... 0x140: /* CONTROL_PADCONF - only used in the POP */ + pad_offset = (addr - 0x30) >> 2; + byte_offset = (addr - 0x30) & (4 - 1); + + value = s->padconf[pad_offset]; + value = (value >> (byte_offset * 8)) & 0xff; + + return value; + + default: + break; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static uint32_t omap_sysctl_read(void *opaque, hwaddr addr) +{ + struct omap_sysctl_s *s = (struct omap_sysctl_s *) opaque; + + switch (addr) { + case 0x000: /* CONTROL_REVISION */ + return 0x20; + + case 0x010: /* CONTROL_SYSCONFIG */ + return s->sysconfig; + + case 0x030 ... 0x140: /* CONTROL_PADCONF - only used in the POP */ + return s->padconf[(addr - 0x30) >> 2]; + + case 0x270: /* CONTROL_DEBOBS */ + return s->obs; + + case 0x274: /* CONTROL_DEVCONF */ + return s->devconfig; + + case 0x28c: /* CONTROL_EMU_SUPPORT */ + return 0; + + case 0x290: /* CONTROL_MSUSPENDMUX_0 */ + return s->msuspendmux[0]; + case 0x294: /* CONTROL_MSUSPENDMUX_1 */ + return s->msuspendmux[1]; + case 0x298: /* CONTROL_MSUSPENDMUX_2 */ + return s->msuspendmux[2]; + case 0x29c: /* CONTROL_MSUSPENDMUX_3 */ + return s->msuspendmux[3]; + case 0x2a0: /* CONTROL_MSUSPENDMUX_4 */ + return s->msuspendmux[4]; + case 0x2a4: /* CONTROL_MSUSPENDMUX_5 */ + return 0; + + case 0x2b8: /* CONTROL_PSA_CTRL */ + return s->psaconfig; + case 0x2bc: /* CONTROL_PSA_CMD */ + case 0x2c0: /* CONTROL_PSA_VALUE */ + return 0; + + case 0x2b0: /* CONTROL_SEC_CTRL */ + return 0x800000f1; + case 0x2d0: /* CONTROL_SEC_EMU */ + return 0x80000015; + case 0x2d4: /* CONTROL_SEC_TAP */ + return 0x8000007f; + case 0x2b4: /* CONTROL_SEC_TEST */ + case 0x2f0: /* CONTROL_SEC_STATUS */ + case 0x2f4: /* CONTROL_SEC_ERR_STATUS */ + /* Secure mode is not present on general-pusrpose device. Outside + * secure mode these values cannot be read or written. */ + return 0; + + case 0x2d8: /* CONTROL_OCM_RAM_PERM */ + return 0xff; + case 0x2dc: /* CONTROL_OCM_PUB_RAM_ADD */ + case 0x2e0: /* CONTROL_EXT_SEC_RAM_START_ADD */ + case 0x2e4: /* CONTROL_EXT_SEC_RAM_STOP_ADD */ + /* No secure mode so no Extended Secure RAM present. */ + return 0; + + case 0x2f8: /* CONTROL_STATUS */ + /* Device Type => General-purpose */ + return 0x0300; + case 0x2fc: /* CONTROL_GENERAL_PURPOSE_STATUS */ + + case 0x300: /* CONTROL_RPUB_KEY_H_0 */ + case 0x304: /* CONTROL_RPUB_KEY_H_1 */ + case 0x308: /* CONTROL_RPUB_KEY_H_2 */ + case 0x30c: /* CONTROL_RPUB_KEY_H_3 */ + return 0xdecafbad; + + case 0x310: /* CONTROL_RAND_KEY_0 */ + case 0x314: /* CONTROL_RAND_KEY_1 */ + case 0x318: /* CONTROL_RAND_KEY_2 */ + case 0x31c: /* CONTROL_RAND_KEY_3 */ + case 0x320: /* CONTROL_CUST_KEY_0 */ + case 0x324: /* CONTROL_CUST_KEY_1 */ + case 0x330: /* CONTROL_TEST_KEY_0 */ + case 0x334: /* CONTROL_TEST_KEY_1 */ + case 0x338: /* CONTROL_TEST_KEY_2 */ + case 0x33c: /* CONTROL_TEST_KEY_3 */ + case 0x340: /* CONTROL_TEST_KEY_4 */ + case 0x344: /* CONTROL_TEST_KEY_5 */ + case 0x348: /* CONTROL_TEST_KEY_6 */ + case 0x34c: /* CONTROL_TEST_KEY_7 */ + case 0x350: /* CONTROL_TEST_KEY_8 */ + case 0x354: /* CONTROL_TEST_KEY_9 */ + /* Can only be accessed in secure mode and when C_FieldAccEnable + * bit is set in CONTROL_SEC_CTRL. + * TODO: otherwise an interconnect access error is generated. */ + return 0; + } + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_sysctl_write8(void *opaque, hwaddr addr, + uint32_t value) +{ + struct omap_sysctl_s *s = (struct omap_sysctl_s *) opaque; + int pad_offset, byte_offset; + int prev_value; + + switch (addr) { + case 0x030 ... 0x140: /* CONTROL_PADCONF - only used in the POP */ + pad_offset = (addr - 0x30) >> 2; + byte_offset = (addr - 0x30) & (4 - 1); + + prev_value = s->padconf[pad_offset]; + prev_value &= ~(0xff << (byte_offset * 8)); + prev_value |= ((value & 0x1f1f1f1f) << (byte_offset * 8)) & 0x1f1f1f1f; + s->padconf[pad_offset] = prev_value; + break; + + default: + OMAP_BAD_REG(addr); + break; + } +} + +static void omap_sysctl_write(void *opaque, hwaddr addr, + uint32_t value) +{ + struct omap_sysctl_s *s = (struct omap_sysctl_s *) opaque; + + switch (addr) { + case 0x000: /* CONTROL_REVISION */ + case 0x2a4: /* CONTROL_MSUSPENDMUX_5 */ + case 0x2c0: /* CONTROL_PSA_VALUE */ + case 0x2f8: /* CONTROL_STATUS */ + case 0x2fc: /* CONTROL_GENERAL_PURPOSE_STATUS */ + case 0x300: /* CONTROL_RPUB_KEY_H_0 */ + case 0x304: /* CONTROL_RPUB_KEY_H_1 */ + case 0x308: /* CONTROL_RPUB_KEY_H_2 */ + case 0x30c: /* CONTROL_RPUB_KEY_H_3 */ + case 0x310: /* CONTROL_RAND_KEY_0 */ + case 0x314: /* CONTROL_RAND_KEY_1 */ + case 0x318: /* CONTROL_RAND_KEY_2 */ + case 0x31c: /* CONTROL_RAND_KEY_3 */ + case 0x320: /* CONTROL_CUST_KEY_0 */ + case 0x324: /* CONTROL_CUST_KEY_1 */ + case 0x330: /* CONTROL_TEST_KEY_0 */ + case 0x334: /* CONTROL_TEST_KEY_1 */ + case 0x338: /* CONTROL_TEST_KEY_2 */ + case 0x33c: /* CONTROL_TEST_KEY_3 */ + case 0x340: /* CONTROL_TEST_KEY_4 */ + case 0x344: /* CONTROL_TEST_KEY_5 */ + case 0x348: /* CONTROL_TEST_KEY_6 */ + case 0x34c: /* CONTROL_TEST_KEY_7 */ + case 0x350: /* CONTROL_TEST_KEY_8 */ + case 0x354: /* CONTROL_TEST_KEY_9 */ + OMAP_RO_REG(addr); + return; + + case 0x010: /* CONTROL_SYSCONFIG */ + s->sysconfig = value & 0x1e; + break; + + case 0x030 ... 0x140: /* CONTROL_PADCONF - only used in the POP */ + /* XXX: should check constant bits */ + s->padconf[(addr - 0x30) >> 2] = value & 0x1f1f1f1f; + break; + + case 0x270: /* CONTROL_DEBOBS */ + s->obs = value & 0xff; + break; + + case 0x274: /* CONTROL_DEVCONF */ + s->devconfig = value & 0xffffc7ff; + break; + + case 0x28c: /* CONTROL_EMU_SUPPORT */ + break; + + case 0x290: /* CONTROL_MSUSPENDMUX_0 */ + s->msuspendmux[0] = value & 0x3fffffff; + break; + case 0x294: /* CONTROL_MSUSPENDMUX_1 */ + s->msuspendmux[1] = value & 0x3fffffff; + break; + case 0x298: /* CONTROL_MSUSPENDMUX_2 */ + s->msuspendmux[2] = value & 0x3fffffff; + break; + case 0x29c: /* CONTROL_MSUSPENDMUX_3 */ + s->msuspendmux[3] = value & 0x3fffffff; + break; + case 0x2a0: /* CONTROL_MSUSPENDMUX_4 */ + s->msuspendmux[4] = value & 0x3fffffff; + break; + + case 0x2b8: /* CONTROL_PSA_CTRL */ + s->psaconfig = value & 0x1c; + s->psaconfig |= (value & 0x20) ? 2 : 1; + break; + case 0x2bc: /* CONTROL_PSA_CMD */ + break; + + case 0x2b0: /* CONTROL_SEC_CTRL */ + case 0x2b4: /* CONTROL_SEC_TEST */ + case 0x2d0: /* CONTROL_SEC_EMU */ + case 0x2d4: /* CONTROL_SEC_TAP */ + case 0x2d8: /* CONTROL_OCM_RAM_PERM */ + case 0x2dc: /* CONTROL_OCM_PUB_RAM_ADD */ + case 0x2e0: /* CONTROL_EXT_SEC_RAM_START_ADD */ + case 0x2e4: /* CONTROL_EXT_SEC_RAM_STOP_ADD */ + case 0x2f0: /* CONTROL_SEC_STATUS */ + case 0x2f4: /* CONTROL_SEC_ERR_STATUS */ + break; + + default: + OMAP_BAD_REG(addr); + return; + } +} + +static const MemoryRegionOps omap_sysctl_ops = { + .old_mmio = { + .read = { + omap_sysctl_read8, + omap_badwidth_read32, /* TODO */ + omap_sysctl_read, + }, + .write = { + omap_sysctl_write8, + omap_badwidth_write32, /* TODO */ + omap_sysctl_write, + }, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap_sysctl_reset(struct omap_sysctl_s *s) +{ + /* (power-on reset) */ + s->sysconfig = 0; + s->obs = 0; + s->devconfig = 0x0c000000; + s->msuspendmux[0] = 0x00000000; + s->msuspendmux[1] = 0x00000000; + s->msuspendmux[2] = 0x00000000; + s->msuspendmux[3] = 0x00000000; + s->msuspendmux[4] = 0x00000000; + s->psaconfig = 1; + + s->padconf[0x00] = 0x000f0f0f; + s->padconf[0x01] = 0x00000000; + s->padconf[0x02] = 0x00000000; + s->padconf[0x03] = 0x00000000; + s->padconf[0x04] = 0x00000000; + s->padconf[0x05] = 0x00000000; + s->padconf[0x06] = 0x00000000; + s->padconf[0x07] = 0x00000000; + s->padconf[0x08] = 0x08080800; + s->padconf[0x09] = 0x08080808; + s->padconf[0x0a] = 0x08080808; + s->padconf[0x0b] = 0x08080808; + s->padconf[0x0c] = 0x08080808; + s->padconf[0x0d] = 0x08080800; + s->padconf[0x0e] = 0x08080808; + s->padconf[0x0f] = 0x08080808; + s->padconf[0x10] = 0x18181808; /* | 0x07070700 if SBoot3 */ + s->padconf[0x11] = 0x18181818; /* | 0x07070707 if SBoot3 */ + s->padconf[0x12] = 0x18181818; /* | 0x07070707 if SBoot3 */ + s->padconf[0x13] = 0x18181818; /* | 0x07070707 if SBoot3 */ + s->padconf[0x14] = 0x18181818; /* | 0x00070707 if SBoot3 */ + s->padconf[0x15] = 0x18181818; + s->padconf[0x16] = 0x18181818; /* | 0x07000000 if SBoot3 */ + s->padconf[0x17] = 0x1f001f00; + s->padconf[0x18] = 0x1f1f1f1f; + s->padconf[0x19] = 0x00000000; + s->padconf[0x1a] = 0x1f180000; + s->padconf[0x1b] = 0x00001f1f; + s->padconf[0x1c] = 0x1f001f00; + s->padconf[0x1d] = 0x00000000; + s->padconf[0x1e] = 0x00000000; + s->padconf[0x1f] = 0x08000000; + s->padconf[0x20] = 0x08080808; + s->padconf[0x21] = 0x08080808; + s->padconf[0x22] = 0x0f080808; + s->padconf[0x23] = 0x0f0f0f0f; + s->padconf[0x24] = 0x000f0f0f; + s->padconf[0x25] = 0x1f1f1f0f; + s->padconf[0x26] = 0x080f0f1f; + s->padconf[0x27] = 0x070f1808; + s->padconf[0x28] = 0x0f070707; + s->padconf[0x29] = 0x000f0f1f; + s->padconf[0x2a] = 0x0f0f0f1f; + s->padconf[0x2b] = 0x08000000; + s->padconf[0x2c] = 0x0000001f; + s->padconf[0x2d] = 0x0f0f1f00; + s->padconf[0x2e] = 0x1f1f0f0f; + s->padconf[0x2f] = 0x0f1f1f1f; + s->padconf[0x30] = 0x0f0f0f0f; + s->padconf[0x31] = 0x0f1f0f1f; + s->padconf[0x32] = 0x0f0f0f0f; + s->padconf[0x33] = 0x0f1f0f1f; + s->padconf[0x34] = 0x1f1f0f0f; + s->padconf[0x35] = 0x0f0f1f1f; + s->padconf[0x36] = 0x0f0f1f0f; + s->padconf[0x37] = 0x0f0f0f0f; + s->padconf[0x38] = 0x1f18180f; + s->padconf[0x39] = 0x1f1f1f1f; + s->padconf[0x3a] = 0x00001f1f; + s->padconf[0x3b] = 0x00000000; + s->padconf[0x3c] = 0x00000000; + s->padconf[0x3d] = 0x0f0f0f0f; + s->padconf[0x3e] = 0x18000f0f; + s->padconf[0x3f] = 0x00070000; + s->padconf[0x40] = 0x00000707; + s->padconf[0x41] = 0x0f1f0700; + s->padconf[0x42] = 0x1f1f070f; + s->padconf[0x43] = 0x0008081f; + s->padconf[0x44] = 0x00000800; +} + +static struct omap_sysctl_s *omap_sysctl_init(struct omap_target_agent_s *ta, + omap_clk iclk, struct omap_mpu_state_s *mpu) +{ + struct omap_sysctl_s *s = (struct omap_sysctl_s *) + g_malloc0(sizeof(struct omap_sysctl_s)); + + s->mpu = mpu; + omap_sysctl_reset(s); + + memory_region_init_io(&s->iomem, NULL, &omap_sysctl_ops, s, "omap.sysctl", + omap_l4_region_size(ta, 0)); + omap_l4_attach(ta, 0, &s->iomem); + + return s; +} + +/* General chip reset */ +static void omap2_mpu_reset(void *opaque) +{ + struct omap_mpu_state_s *mpu = (struct omap_mpu_state_s *) opaque; + + omap_dma_reset(mpu->dma); + omap_prcm_reset(mpu->prcm); + omap_sysctl_reset(mpu->sysc); + omap_gp_timer_reset(mpu->gptimer[0]); + omap_gp_timer_reset(mpu->gptimer[1]); + omap_gp_timer_reset(mpu->gptimer[2]); + omap_gp_timer_reset(mpu->gptimer[3]); + omap_gp_timer_reset(mpu->gptimer[4]); + omap_gp_timer_reset(mpu->gptimer[5]); + omap_gp_timer_reset(mpu->gptimer[6]); + omap_gp_timer_reset(mpu->gptimer[7]); + omap_gp_timer_reset(mpu->gptimer[8]); + omap_gp_timer_reset(mpu->gptimer[9]); + omap_gp_timer_reset(mpu->gptimer[10]); + omap_gp_timer_reset(mpu->gptimer[11]); + omap_synctimer_reset(mpu->synctimer); + omap_sdrc_reset(mpu->sdrc); + omap_gpmc_reset(mpu->gpmc); + omap_dss_reset(mpu->dss); + omap_uart_reset(mpu->uart[0]); + omap_uart_reset(mpu->uart[1]); + omap_uart_reset(mpu->uart[2]); + omap_mmc_reset(mpu->mmc); + omap_mcspi_reset(mpu->mcspi[0]); + omap_mcspi_reset(mpu->mcspi[1]); + cpu_reset(CPU(mpu->cpu)); +} + +static int omap2_validate_addr(struct omap_mpu_state_s *s, + hwaddr addr) +{ + return 1; +} + +static const struct dma_irq_map omap2_dma_irq_map[] = { + { 0, OMAP_INT_24XX_SDMA_IRQ0 }, + { 0, OMAP_INT_24XX_SDMA_IRQ1 }, + { 0, OMAP_INT_24XX_SDMA_IRQ2 }, + { 0, OMAP_INT_24XX_SDMA_IRQ3 }, +}; + +struct omap_mpu_state_s *omap2420_mpu_init(MemoryRegion *sysmem, + unsigned long sdram_size, + const char *core) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) + g_malloc0(sizeof(struct omap_mpu_state_s)); + qemu_irq dma_irqs[4]; + DriveInfo *dinfo; + int i; + SysBusDevice *busdev; + struct omap_target_agent_s *ta; + + /* Core */ + s->mpu_model = omap2420; + s->cpu = cpu_arm_init(core ?: "arm1136-r2"); + if (s->cpu == NULL) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + s->sdram_size = sdram_size; + s->sram_size = OMAP242X_SRAM_SIZE; + + s->wakeup = qemu_allocate_irq(omap_mpu_wakeup, s, 0); + + /* Clocks */ + omap_clk_init(s); + + /* Memory-mapped stuff */ + memory_region_allocate_system_memory(&s->sdram, NULL, "omap2.dram", + s->sdram_size); + memory_region_add_subregion(sysmem, OMAP2_Q2_BASE, &s->sdram); + memory_region_init_ram(&s->sram, NULL, "omap2.sram", s->sram_size, + &error_abort); + vmstate_register_ram_global(&s->sram); + memory_region_add_subregion(sysmem, OMAP2_SRAM_BASE, &s->sram); + + s->l4 = omap_l4_init(sysmem, OMAP2_L4_BASE, 54); + + /* Actually mapped at any 2K boundary in the ARM11 private-peripheral if */ + s->ih[0] = qdev_create(NULL, "omap2-intc"); + qdev_prop_set_uint8(s->ih[0], "revision", 0x21); + qdev_prop_set_ptr(s->ih[0], "fclk", omap_findclk(s, "mpu_intc_fclk")); + qdev_prop_set_ptr(s->ih[0], "iclk", omap_findclk(s, "mpu_intc_iclk")); + qdev_init_nofail(s->ih[0]); + busdev = SYS_BUS_DEVICE(s->ih[0]); + sysbus_connect_irq(busdev, 0, + qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_IRQ)); + sysbus_connect_irq(busdev, 1, + qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_FIQ)); + sysbus_mmio_map(busdev, 0, 0x480fe000); + s->prcm = omap_prcm_init(omap_l4tao(s->l4, 3), + qdev_get_gpio_in(s->ih[0], + OMAP_INT_24XX_PRCM_MPU_IRQ), + NULL, NULL, s); + + s->sysc = omap_sysctl_init(omap_l4tao(s->l4, 1), + omap_findclk(s, "omapctrl_iclk"), s); + + for (i = 0; i < 4; i++) { + dma_irqs[i] = qdev_get_gpio_in(s->ih[omap2_dma_irq_map[i].ih], + omap2_dma_irq_map[i].intr); + } + s->dma = omap_dma4_init(0x48056000, dma_irqs, sysmem, s, 256, 32, + omap_findclk(s, "sdma_iclk"), + omap_findclk(s, "sdma_fclk")); + s->port->addr_valid = omap2_validate_addr; + + /* Register SDRAM and SRAM ports for fast DMA transfers. */ + soc_dma_port_add_mem(s->dma, memory_region_get_ram_ptr(&s->sdram), + OMAP2_Q2_BASE, s->sdram_size); + soc_dma_port_add_mem(s->dma, memory_region_get_ram_ptr(&s->sram), + OMAP2_SRAM_BASE, s->sram_size); + + s->uart[0] = omap2_uart_init(sysmem, omap_l4ta(s->l4, 19), + qdev_get_gpio_in(s->ih[0], + OMAP_INT_24XX_UART1_IRQ), + omap_findclk(s, "uart1_fclk"), + omap_findclk(s, "uart1_iclk"), + s->drq[OMAP24XX_DMA_UART1_TX], + s->drq[OMAP24XX_DMA_UART1_RX], + "uart1", + serial_hds[0]); + s->uart[1] = omap2_uart_init(sysmem, omap_l4ta(s->l4, 20), + qdev_get_gpio_in(s->ih[0], + OMAP_INT_24XX_UART2_IRQ), + omap_findclk(s, "uart2_fclk"), + omap_findclk(s, "uart2_iclk"), + s->drq[OMAP24XX_DMA_UART2_TX], + s->drq[OMAP24XX_DMA_UART2_RX], + "uart2", + serial_hds[0] ? serial_hds[1] : NULL); + s->uart[2] = omap2_uart_init(sysmem, omap_l4ta(s->l4, 21), + qdev_get_gpio_in(s->ih[0], + OMAP_INT_24XX_UART3_IRQ), + omap_findclk(s, "uart3_fclk"), + omap_findclk(s, "uart3_iclk"), + s->drq[OMAP24XX_DMA_UART3_TX], + s->drq[OMAP24XX_DMA_UART3_RX], + "uart3", + serial_hds[0] && serial_hds[1] ? serial_hds[2] : NULL); + + s->gptimer[0] = omap_gp_timer_init(omap_l4ta(s->l4, 7), + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER1), + omap_findclk(s, "wu_gpt1_clk"), + omap_findclk(s, "wu_l4_iclk")); + s->gptimer[1] = omap_gp_timer_init(omap_l4ta(s->l4, 8), + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER2), + omap_findclk(s, "core_gpt2_clk"), + omap_findclk(s, "core_l4_iclk")); + s->gptimer[2] = omap_gp_timer_init(omap_l4ta(s->l4, 22), + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER3), + omap_findclk(s, "core_gpt3_clk"), + omap_findclk(s, "core_l4_iclk")); + s->gptimer[3] = omap_gp_timer_init(omap_l4ta(s->l4, 23), + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER4), + omap_findclk(s, "core_gpt4_clk"), + omap_findclk(s, "core_l4_iclk")); + s->gptimer[4] = omap_gp_timer_init(omap_l4ta(s->l4, 24), + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER5), + omap_findclk(s, "core_gpt5_clk"), + omap_findclk(s, "core_l4_iclk")); + s->gptimer[5] = omap_gp_timer_init(omap_l4ta(s->l4, 25), + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER6), + omap_findclk(s, "core_gpt6_clk"), + omap_findclk(s, "core_l4_iclk")); + s->gptimer[6] = omap_gp_timer_init(omap_l4ta(s->l4, 26), + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER7), + omap_findclk(s, "core_gpt7_clk"), + omap_findclk(s, "core_l4_iclk")); + s->gptimer[7] = omap_gp_timer_init(omap_l4ta(s->l4, 27), + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER8), + omap_findclk(s, "core_gpt8_clk"), + omap_findclk(s, "core_l4_iclk")); + s->gptimer[8] = omap_gp_timer_init(omap_l4ta(s->l4, 28), + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER9), + omap_findclk(s, "core_gpt9_clk"), + omap_findclk(s, "core_l4_iclk")); + s->gptimer[9] = omap_gp_timer_init(omap_l4ta(s->l4, 29), + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER10), + omap_findclk(s, "core_gpt10_clk"), + omap_findclk(s, "core_l4_iclk")); + s->gptimer[10] = omap_gp_timer_init(omap_l4ta(s->l4, 30), + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER11), + omap_findclk(s, "core_gpt11_clk"), + omap_findclk(s, "core_l4_iclk")); + s->gptimer[11] = omap_gp_timer_init(omap_l4ta(s->l4, 31), + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPTIMER12), + omap_findclk(s, "core_gpt12_clk"), + omap_findclk(s, "core_l4_iclk")); + + omap_tap_init(omap_l4ta(s->l4, 2), s); + + s->synctimer = omap_synctimer_init(omap_l4tao(s->l4, 2), s, + omap_findclk(s, "clk32-kHz"), + omap_findclk(s, "core_l4_iclk")); + + s->i2c[0] = qdev_create(NULL, "omap_i2c"); + qdev_prop_set_uint8(s->i2c[0], "revision", 0x34); + qdev_prop_set_ptr(s->i2c[0], "iclk", omap_findclk(s, "i2c1.iclk")); + qdev_prop_set_ptr(s->i2c[0], "fclk", omap_findclk(s, "i2c1.fclk")); + qdev_init_nofail(s->i2c[0]); + busdev = SYS_BUS_DEVICE(s->i2c[0]); + sysbus_connect_irq(busdev, 0, + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_I2C1_IRQ)); + sysbus_connect_irq(busdev, 1, s->drq[OMAP24XX_DMA_I2C1_TX]); + sysbus_connect_irq(busdev, 2, s->drq[OMAP24XX_DMA_I2C1_RX]); + sysbus_mmio_map(busdev, 0, omap_l4_region_base(omap_l4tao(s->l4, 5), 0)); + + s->i2c[1] = qdev_create(NULL, "omap_i2c"); + qdev_prop_set_uint8(s->i2c[1], "revision", 0x34); + qdev_prop_set_ptr(s->i2c[1], "iclk", omap_findclk(s, "i2c2.iclk")); + qdev_prop_set_ptr(s->i2c[1], "fclk", omap_findclk(s, "i2c2.fclk")); + qdev_init_nofail(s->i2c[1]); + busdev = SYS_BUS_DEVICE(s->i2c[1]); + sysbus_connect_irq(busdev, 0, + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_I2C2_IRQ)); + sysbus_connect_irq(busdev, 1, s->drq[OMAP24XX_DMA_I2C2_TX]); + sysbus_connect_irq(busdev, 2, s->drq[OMAP24XX_DMA_I2C2_RX]); + sysbus_mmio_map(busdev, 0, omap_l4_region_base(omap_l4tao(s->l4, 6), 0)); + + s->gpio = qdev_create(NULL, "omap2-gpio"); + qdev_prop_set_int32(s->gpio, "mpu_model", s->mpu_model); + qdev_prop_set_ptr(s->gpio, "iclk", omap_findclk(s, "gpio_iclk")); + qdev_prop_set_ptr(s->gpio, "fclk0", omap_findclk(s, "gpio1_dbclk")); + qdev_prop_set_ptr(s->gpio, "fclk1", omap_findclk(s, "gpio2_dbclk")); + qdev_prop_set_ptr(s->gpio, "fclk2", omap_findclk(s, "gpio3_dbclk")); + qdev_prop_set_ptr(s->gpio, "fclk3", omap_findclk(s, "gpio4_dbclk")); + if (s->mpu_model == omap2430) { + qdev_prop_set_ptr(s->gpio, "fclk4", omap_findclk(s, "gpio5_dbclk")); + } + qdev_init_nofail(s->gpio); + busdev = SYS_BUS_DEVICE(s->gpio); + sysbus_connect_irq(busdev, 0, + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPIO_BANK1)); + sysbus_connect_irq(busdev, 3, + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPIO_BANK2)); + sysbus_connect_irq(busdev, 6, + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPIO_BANK3)); + sysbus_connect_irq(busdev, 9, + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPIO_BANK4)); + if (s->mpu_model == omap2430) { + sysbus_connect_irq(busdev, 12, + qdev_get_gpio_in(s->ih[0], + OMAP_INT_243X_GPIO_BANK5)); + } + ta = omap_l4ta(s->l4, 3); + sysbus_mmio_map(busdev, 0, omap_l4_region_base(ta, 1)); + sysbus_mmio_map(busdev, 1, omap_l4_region_base(ta, 0)); + sysbus_mmio_map(busdev, 2, omap_l4_region_base(ta, 2)); + sysbus_mmio_map(busdev, 3, omap_l4_region_base(ta, 4)); + sysbus_mmio_map(busdev, 4, omap_l4_region_base(ta, 5)); + + s->sdrc = omap_sdrc_init(sysmem, 0x68009000); + s->gpmc = omap_gpmc_init(s, 0x6800a000, + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_GPMC_IRQ), + s->drq[OMAP24XX_DMA_GPMC]); + + dinfo = drive_get(IF_SD, 0, 0); + if (!dinfo) { + fprintf(stderr, "qemu: missing SecureDigital device\n"); + exit(1); + } + s->mmc = omap2_mmc_init(omap_l4tao(s->l4, 9), + blk_by_legacy_dinfo(dinfo), + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_MMC_IRQ), + &s->drq[OMAP24XX_DMA_MMC1_TX], + omap_findclk(s, "mmc_fclk"), omap_findclk(s, "mmc_iclk")); + + s->mcspi[0] = omap_mcspi_init(omap_l4ta(s->l4, 35), 4, + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_MCSPI1_IRQ), + &s->drq[OMAP24XX_DMA_SPI1_TX0], + omap_findclk(s, "spi1_fclk"), + omap_findclk(s, "spi1_iclk")); + s->mcspi[1] = omap_mcspi_init(omap_l4ta(s->l4, 36), 2, + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_MCSPI2_IRQ), + &s->drq[OMAP24XX_DMA_SPI2_TX0], + omap_findclk(s, "spi2_fclk"), + omap_findclk(s, "spi2_iclk")); + + s->dss = omap_dss_init(omap_l4ta(s->l4, 10), sysmem, 0x68000800, + /* XXX wire M_IRQ_25, D_L2_IRQ_30 and I_IRQ_13 together */ + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_DSS_IRQ), + s->drq[OMAP24XX_DMA_DSS], + omap_findclk(s, "dss_clk1"), omap_findclk(s, "dss_clk2"), + omap_findclk(s, "dss_54m_clk"), + omap_findclk(s, "dss_l3_iclk"), + omap_findclk(s, "dss_l4_iclk")); + + omap_sti_init(omap_l4ta(s->l4, 18), sysmem, 0x54000000, + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_STI), + omap_findclk(s, "emul_ck"), + serial_hds[0] && serial_hds[1] && serial_hds[2] ? + serial_hds[3] : NULL); + + s->eac = omap_eac_init(omap_l4ta(s->l4, 32), + qdev_get_gpio_in(s->ih[0], OMAP_INT_24XX_EAC_IRQ), + /* Ten consecutive lines */ + &s->drq[OMAP24XX_DMA_EAC_AC_RD], + omap_findclk(s, "func_96m_clk"), + omap_findclk(s, "core_l4_iclk")); + + /* All register mappings (includin those not currenlty implemented): + * SystemControlMod 48000000 - 48000fff + * SystemControlL4 48001000 - 48001fff + * 32kHz Timer Mod 48004000 - 48004fff + * 32kHz Timer L4 48005000 - 48005fff + * PRCM ModA 48008000 - 480087ff + * PRCM ModB 48008800 - 48008fff + * PRCM L4 48009000 - 48009fff + * TEST-BCM Mod 48012000 - 48012fff + * TEST-BCM L4 48013000 - 48013fff + * TEST-TAP Mod 48014000 - 48014fff + * TEST-TAP L4 48015000 - 48015fff + * GPIO1 Mod 48018000 - 48018fff + * GPIO Top 48019000 - 48019fff + * GPIO2 Mod 4801a000 - 4801afff + * GPIO L4 4801b000 - 4801bfff + * GPIO3 Mod 4801c000 - 4801cfff + * GPIO4 Mod 4801e000 - 4801efff + * WDTIMER1 Mod 48020000 - 48010fff + * WDTIMER Top 48021000 - 48011fff + * WDTIMER2 Mod 48022000 - 48012fff + * WDTIMER L4 48023000 - 48013fff + * WDTIMER3 Mod 48024000 - 48014fff + * WDTIMER3 L4 48025000 - 48015fff + * WDTIMER4 Mod 48026000 - 48016fff + * WDTIMER4 L4 48027000 - 48017fff + * GPTIMER1 Mod 48028000 - 48018fff + * GPTIMER1 L4 48029000 - 48019fff + * GPTIMER2 Mod 4802a000 - 4801afff + * GPTIMER2 L4 4802b000 - 4801bfff + * L4-Config AP 48040000 - 480407ff + * L4-Config IP 48040800 - 48040fff + * L4-Config LA 48041000 - 48041fff + * ARM11ETB Mod 48048000 - 48049fff + * ARM11ETB L4 4804a000 - 4804afff + * DISPLAY Top 48050000 - 480503ff + * DISPLAY DISPC 48050400 - 480507ff + * DISPLAY RFBI 48050800 - 48050bff + * DISPLAY VENC 48050c00 - 48050fff + * DISPLAY L4 48051000 - 48051fff + * CAMERA Top 48052000 - 480523ff + * CAMERA core 48052400 - 480527ff + * CAMERA DMA 48052800 - 48052bff + * CAMERA MMU 48052c00 - 48052fff + * CAMERA L4 48053000 - 48053fff + * SDMA Mod 48056000 - 48056fff + * SDMA L4 48057000 - 48057fff + * SSI Top 48058000 - 48058fff + * SSI GDD 48059000 - 48059fff + * SSI Port1 4805a000 - 4805afff + * SSI Port2 4805b000 - 4805bfff + * SSI L4 4805c000 - 4805cfff + * USB Mod 4805e000 - 480fefff + * USB L4 4805f000 - 480fffff + * WIN_TRACER1 Mod 48060000 - 48060fff + * WIN_TRACER1 L4 48061000 - 48061fff + * WIN_TRACER2 Mod 48062000 - 48062fff + * WIN_TRACER2 L4 48063000 - 48063fff + * WIN_TRACER3 Mod 48064000 - 48064fff + * WIN_TRACER3 L4 48065000 - 48065fff + * WIN_TRACER4 Top 48066000 - 480660ff + * WIN_TRACER4 ETT 48066100 - 480661ff + * WIN_TRACER4 WT 48066200 - 480662ff + * WIN_TRACER4 L4 48067000 - 48067fff + * XTI Mod 48068000 - 48068fff + * XTI L4 48069000 - 48069fff + * UART1 Mod 4806a000 - 4806afff + * UART1 L4 4806b000 - 4806bfff + * UART2 Mod 4806c000 - 4806cfff + * UART2 L4 4806d000 - 4806dfff + * UART3 Mod 4806e000 - 4806efff + * UART3 L4 4806f000 - 4806ffff + * I2C1 Mod 48070000 - 48070fff + * I2C1 L4 48071000 - 48071fff + * I2C2 Mod 48072000 - 48072fff + * I2C2 L4 48073000 - 48073fff + * McBSP1 Mod 48074000 - 48074fff + * McBSP1 L4 48075000 - 48075fff + * McBSP2 Mod 48076000 - 48076fff + * McBSP2 L4 48077000 - 48077fff + * GPTIMER3 Mod 48078000 - 48078fff + * GPTIMER3 L4 48079000 - 48079fff + * GPTIMER4 Mod 4807a000 - 4807afff + * GPTIMER4 L4 4807b000 - 4807bfff + * GPTIMER5 Mod 4807c000 - 4807cfff + * GPTIMER5 L4 4807d000 - 4807dfff + * GPTIMER6 Mod 4807e000 - 4807efff + * GPTIMER6 L4 4807f000 - 4807ffff + * GPTIMER7 Mod 48080000 - 48080fff + * GPTIMER7 L4 48081000 - 48081fff + * GPTIMER8 Mod 48082000 - 48082fff + * GPTIMER8 L4 48083000 - 48083fff + * GPTIMER9 Mod 48084000 - 48084fff + * GPTIMER9 L4 48085000 - 48085fff + * GPTIMER10 Mod 48086000 - 48086fff + * GPTIMER10 L4 48087000 - 48087fff + * GPTIMER11 Mod 48088000 - 48088fff + * GPTIMER11 L4 48089000 - 48089fff + * GPTIMER12 Mod 4808a000 - 4808afff + * GPTIMER12 L4 4808b000 - 4808bfff + * EAC Mod 48090000 - 48090fff + * EAC L4 48091000 - 48091fff + * FAC Mod 48092000 - 48092fff + * FAC L4 48093000 - 48093fff + * MAILBOX Mod 48094000 - 48094fff + * MAILBOX L4 48095000 - 48095fff + * SPI1 Mod 48098000 - 48098fff + * SPI1 L4 48099000 - 48099fff + * SPI2 Mod 4809a000 - 4809afff + * SPI2 L4 4809b000 - 4809bfff + * MMC/SDIO Mod 4809c000 - 4809cfff + * MMC/SDIO L4 4809d000 - 4809dfff + * MS_PRO Mod 4809e000 - 4809efff + * MS_PRO L4 4809f000 - 4809ffff + * RNG Mod 480a0000 - 480a0fff + * RNG L4 480a1000 - 480a1fff + * DES3DES Mod 480a2000 - 480a2fff + * DES3DES L4 480a3000 - 480a3fff + * SHA1MD5 Mod 480a4000 - 480a4fff + * SHA1MD5 L4 480a5000 - 480a5fff + * AES Mod 480a6000 - 480a6fff + * AES L4 480a7000 - 480a7fff + * PKA Mod 480a8000 - 480a9fff + * PKA L4 480aa000 - 480aafff + * MG Mod 480b0000 - 480b0fff + * MG L4 480b1000 - 480b1fff + * HDQ/1-wire Mod 480b2000 - 480b2fff + * HDQ/1-wire L4 480b3000 - 480b3fff + * MPU interrupt 480fe000 - 480fefff + * STI channel base 54000000 - 5400ffff + * IVA RAM 5c000000 - 5c01ffff + * IVA ROM 5c020000 - 5c027fff + * IMG_BUF_A 5c040000 - 5c040fff + * IMG_BUF_B 5c042000 - 5c042fff + * VLCDS 5c048000 - 5c0487ff + * IMX_COEF 5c049000 - 5c04afff + * IMX_CMD 5c051000 - 5c051fff + * VLCDQ 5c053000 - 5c0533ff + * VLCDH 5c054000 - 5c054fff + * SEQ_CMD 5c055000 - 5c055fff + * IMX_REG 5c056000 - 5c0560ff + * VLCD_REG 5c056100 - 5c0561ff + * SEQ_REG 5c056200 - 5c0562ff + * IMG_BUF_REG 5c056300 - 5c0563ff + * SEQIRQ_REG 5c056400 - 5c0564ff + * OCP_REG 5c060000 - 5c060fff + * SYSC_REG 5c070000 - 5c070fff + * MMU_REG 5d000000 - 5d000fff + * sDMA R 68000400 - 680005ff + * sDMA W 68000600 - 680007ff + * Display Control 68000800 - 680009ff + * DSP subsystem 68000a00 - 68000bff + * MPU subsystem 68000c00 - 68000dff + * IVA subsystem 68001000 - 680011ff + * USB 68001200 - 680013ff + * Camera 68001400 - 680015ff + * VLYNQ (firewall) 68001800 - 68001bff + * VLYNQ 68001e00 - 68001fff + * SSI 68002000 - 680021ff + * L4 68002400 - 680025ff + * DSP (firewall) 68002800 - 68002bff + * DSP subsystem 68002e00 - 68002fff + * IVA (firewall) 68003000 - 680033ff + * IVA 68003600 - 680037ff + * GFX 68003a00 - 68003bff + * CMDWR emulation 68003c00 - 68003dff + * SMS 68004000 - 680041ff + * OCM 68004200 - 680043ff + * GPMC 68004400 - 680045ff + * RAM (firewall) 68005000 - 680053ff + * RAM (err login) 68005400 - 680057ff + * ROM (firewall) 68005800 - 68005bff + * ROM (err login) 68005c00 - 68005fff + * GPMC (firewall) 68006000 - 680063ff + * GPMC (err login) 68006400 - 680067ff + * SMS (err login) 68006c00 - 68006fff + * SMS registers 68008000 - 68008fff + * SDRC registers 68009000 - 68009fff + * GPMC registers 6800a000 6800afff + */ + + qemu_register_reset(omap2_mpu_reset, s); + + return s; +} diff --git a/qemu/hw/arm/omap_sx1.c b/qemu/hw/arm/omap_sx1.c new file mode 100644 index 000000000..4b0f7f9c4 --- /dev/null +++ b/qemu/hw/arm/omap_sx1.c @@ -0,0 +1,238 @@ +/* omap_sx1.c Support for the Siemens SX1 smartphone emulation. + * + * Copyright (C) 2008 + * Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> + * Copyright (C) 2007 Vladimir Ananiev <vovan888@gmail.com> + * + * based on PalmOne's (TM) PDAs support (palm.c) + */ + +/* + * PalmOne's (TM) PDAs. + * + * Copyright (C) 2006-2007 Andrzej Zaborowski <balrog@zabor.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#include "hw/hw.h" +#include "ui/console.h" +#include "hw/arm/omap.h" +#include "hw/boards.h" +#include "hw/arm/arm.h" +#include "hw/block/flash.h" +#include "sysemu/block-backend.h" +#include "sysemu/qtest.h" +#include "exec/address-spaces.h" + +/*****************************************************************************/ +/* Siemens SX1 Cellphone V1 */ +/* - ARM OMAP310 processor + * - SRAM 192 kB + * - SDRAM 32 MB at 0x10000000 + * - Boot flash 16 MB at 0x00000000 + * - Application flash 8 MB at 0x04000000 + * - 3 serial ports + * - 1 SecureDigital + * - 1 LCD display + * - 1 RTC + */ + +/*****************************************************************************/ +/* Siemens SX1 Cellphone V2 */ +/* - ARM OMAP310 processor + * - SRAM 192 kB + * - SDRAM 32 MB at 0x10000000 + * - Boot flash 32 MB at 0x00000000 + * - 3 serial ports + * - 1 SecureDigital + * - 1 LCD display + * - 1 RTC + */ + +static uint64_t static_read(void *opaque, hwaddr offset, + unsigned size) +{ + uint32_t *val = (uint32_t *) opaque; + uint32_t mask = (4 / size) - 1; + + return *val >> ((offset & mask) << 3); +} + +static void static_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ +#ifdef SPY + printf("%s: value %" PRIx64 " %u bytes written at 0x%x\n", + __func__, value, size, (int)offset); +#endif +} + +static const MemoryRegionOps static_ops = { + .read = static_read, + .write = static_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +#define sdram_size 0x02000000 +#define sector_size (128 * 1024) +#define flash0_size (16 * 1024 * 1024) +#define flash1_size ( 8 * 1024 * 1024) +#define flash2_size (32 * 1024 * 1024) +#define total_ram_v1 (sdram_size + flash0_size + flash1_size + OMAP15XX_SRAM_SIZE) +#define total_ram_v2 (sdram_size + flash2_size + OMAP15XX_SRAM_SIZE) + +static struct arm_boot_info sx1_binfo = { + .loader_start = OMAP_EMIFF_BASE, + .ram_size = sdram_size, + .board_id = 0x265, +}; + +static void sx1_init(MachineState *machine, const int version) +{ + struct omap_mpu_state_s *mpu; + MemoryRegion *address_space = get_system_memory(); + MemoryRegion *flash = g_new(MemoryRegion, 1); + MemoryRegion *cs = g_new(MemoryRegion, 4); + static uint32_t cs0val = 0x00213090; + static uint32_t cs1val = 0x00215070; + static uint32_t cs2val = 0x00001139; + static uint32_t cs3val = 0x00001139; + DriveInfo *dinfo; + int fl_idx; + uint32_t flash_size = flash0_size; + int be; + + if (version == 2) { + flash_size = flash2_size; + } + + mpu = omap310_mpu_init(address_space, sx1_binfo.ram_size, + machine->cpu_model); + + /* External Flash (EMIFS) */ + memory_region_init_ram(flash, NULL, "omap_sx1.flash0-0", flash_size, + &error_abort); + vmstate_register_ram_global(flash); + memory_region_set_readonly(flash, true); + memory_region_add_subregion(address_space, OMAP_CS0_BASE, flash); + + memory_region_init_io(&cs[0], NULL, &static_ops, &cs0val, + "sx1.cs0", OMAP_CS0_SIZE - flash_size); + memory_region_add_subregion(address_space, + OMAP_CS0_BASE + flash_size, &cs[0]); + + + memory_region_init_io(&cs[2], NULL, &static_ops, &cs2val, + "sx1.cs2", OMAP_CS2_SIZE); + memory_region_add_subregion(address_space, + OMAP_CS2_BASE, &cs[2]); + + memory_region_init_io(&cs[3], NULL, &static_ops, &cs3val, + "sx1.cs3", OMAP_CS3_SIZE); + memory_region_add_subregion(address_space, + OMAP_CS2_BASE, &cs[3]); + + fl_idx = 0; +#ifdef TARGET_WORDS_BIGENDIAN + be = 1; +#else + be = 0; +#endif + + if ((dinfo = drive_get(IF_PFLASH, 0, fl_idx)) != NULL) { + if (!pflash_cfi01_register(OMAP_CS0_BASE, NULL, + "omap_sx1.flash0-1", flash_size, + blk_by_legacy_dinfo(dinfo), + sector_size, flash_size / sector_size, + 4, 0, 0, 0, 0, be)) { + fprintf(stderr, "qemu: Error registering flash memory %d.\n", + fl_idx); + } + fl_idx++; + } + + if ((version == 1) && + (dinfo = drive_get(IF_PFLASH, 0, fl_idx)) != NULL) { + MemoryRegion *flash_1 = g_new(MemoryRegion, 1); + memory_region_init_ram(flash_1, NULL, "omap_sx1.flash1-0", flash1_size, + &error_abort); + vmstate_register_ram_global(flash_1); + memory_region_set_readonly(flash_1, true); + memory_region_add_subregion(address_space, OMAP_CS1_BASE, flash_1); + + memory_region_init_io(&cs[1], NULL, &static_ops, &cs1val, + "sx1.cs1", OMAP_CS1_SIZE - flash1_size); + memory_region_add_subregion(address_space, + OMAP_CS1_BASE + flash1_size, &cs[1]); + + if (!pflash_cfi01_register(OMAP_CS1_BASE, NULL, + "omap_sx1.flash1-1", flash1_size, + blk_by_legacy_dinfo(dinfo), + sector_size, flash1_size / sector_size, + 4, 0, 0, 0, 0, be)) { + fprintf(stderr, "qemu: Error registering flash memory %d.\n", + fl_idx); + } + fl_idx++; + } else { + memory_region_init_io(&cs[1], NULL, &static_ops, &cs1val, + "sx1.cs1", OMAP_CS1_SIZE); + memory_region_add_subregion(address_space, + OMAP_CS1_BASE, &cs[1]); + } + + if (!machine->kernel_filename && !fl_idx && !qtest_enabled()) { + fprintf(stderr, "Kernel or Flash image must be specified\n"); + exit(1); + } + + /* Load the kernel. */ + sx1_binfo.kernel_filename = machine->kernel_filename; + sx1_binfo.kernel_cmdline = machine->kernel_cmdline; + sx1_binfo.initrd_filename = machine->initrd_filename; + arm_load_kernel(mpu->cpu, &sx1_binfo); + + /* TODO: fix next line */ + //~ qemu_console_resize(ds, 640, 480); +} + +static void sx1_init_v1(MachineState *machine) +{ + sx1_init(machine, 1); +} + +static void sx1_init_v2(MachineState *machine) +{ + sx1_init(machine, 2); +} + +static QEMUMachine sx1_machine_v2 = { + .name = "sx1", + .desc = "Siemens SX1 (OMAP310) V2", + .init = sx1_init_v2, +}; + +static QEMUMachine sx1_machine_v1 = { + .name = "sx1-v1", + .desc = "Siemens SX1 (OMAP310) V1", + .init = sx1_init_v1, +}; + +static void sx1_machine_init(void) +{ + qemu_register_machine(&sx1_machine_v2); + qemu_register_machine(&sx1_machine_v1); +} + +machine_init(sx1_machine_init); diff --git a/qemu/hw/arm/palm.c b/qemu/hw/arm/palm.c new file mode 100644 index 000000000..7f1cfb8f6 --- /dev/null +++ b/qemu/hw/arm/palm.c @@ -0,0 +1,283 @@ +/* + * PalmOne's (TM) PDAs. + * + * Copyright (C) 2006-2007 Andrzej Zaborowski <balrog@zabor.org> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#include "hw/hw.h" +#include "audio/audio.h" +#include "sysemu/sysemu.h" +#include "sysemu/qtest.h" +#include "ui/console.h" +#include "hw/arm/omap.h" +#include "hw/boards.h" +#include "hw/arm/arm.h" +#include "hw/devices.h" +#include "hw/loader.h" +#include "exec/address-spaces.h" + +static uint32_t static_readb(void *opaque, hwaddr offset) +{ + uint32_t *val = (uint32_t *) opaque; + return *val >> ((offset & 3) << 3); +} + +static uint32_t static_readh(void *opaque, hwaddr offset) +{ + uint32_t *val = (uint32_t *) opaque; + return *val >> ((offset & 1) << 3); +} + +static uint32_t static_readw(void *opaque, hwaddr offset) +{ + uint32_t *val = (uint32_t *) opaque; + return *val >> ((offset & 0) << 3); +} + +static void static_write(void *opaque, hwaddr offset, + uint32_t value) +{ +#ifdef SPY + printf("%s: value %08lx written at " PA_FMT "\n", + __FUNCTION__, value, offset); +#endif +} + +static const MemoryRegionOps static_ops = { + .old_mmio = { + .read = { static_readb, static_readh, static_readw, }, + .write = { static_write, static_write, static_write, }, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +/* Palm Tunsgten|E support */ + +/* Shared GPIOs */ +#define PALMTE_USBDETECT_GPIO 0 +#define PALMTE_USB_OR_DC_GPIO 1 +#define PALMTE_TSC_GPIO 4 +#define PALMTE_PINTDAV_GPIO 6 +#define PALMTE_MMC_WP_GPIO 8 +#define PALMTE_MMC_POWER_GPIO 9 +#define PALMTE_HDQ_GPIO 11 +#define PALMTE_HEADPHONES_GPIO 14 +#define PALMTE_SPEAKER_GPIO 15 +/* MPU private GPIOs */ +#define PALMTE_DC_GPIO 2 +#define PALMTE_MMC_SWITCH_GPIO 4 +#define PALMTE_MMC1_GPIO 6 +#define PALMTE_MMC2_GPIO 7 +#define PALMTE_MMC3_GPIO 11 + +static MouseTransformInfo palmte_pointercal = { + .x = 320, + .y = 320, + .a = { -5909, 8, 22465308, 104, 7644, -1219972, 65536 }, +}; + +static void palmte_microwire_setup(struct omap_mpu_state_s *cpu) +{ + uWireSlave *tsc; + + tsc = tsc2102_init(qdev_get_gpio_in(cpu->gpio, PALMTE_PINTDAV_GPIO)); + + omap_uwire_attach(cpu->microwire, tsc, 0); + omap_mcbsp_i2s_attach(cpu->mcbsp1, tsc210x_codec(tsc)); + + tsc210x_set_transform(tsc, &palmte_pointercal); +} + +static struct { + int row; + int column; +} palmte_keymap[0x80] = { + [0 ... 0x7f] = { -1, -1 }, + [0x3b] = { 0, 0 }, /* F1 -> Calendar */ + [0x3c] = { 1, 0 }, /* F2 -> Contacts */ + [0x3d] = { 2, 0 }, /* F3 -> Tasks List */ + [0x3e] = { 3, 0 }, /* F4 -> Note Pad */ + [0x01] = { 4, 0 }, /* Esc -> Power */ + [0x4b] = { 0, 1 }, /* Left */ + [0x50] = { 1, 1 }, /* Down */ + [0x48] = { 2, 1 }, /* Up */ + [0x4d] = { 3, 1 }, /* Right */ + [0x4c] = { 4, 1 }, /* Centre */ + [0x39] = { 4, 1 }, /* Spc -> Centre */ +}; + +static void palmte_button_event(void *opaque, int keycode) +{ + struct omap_mpu_state_s *cpu = (struct omap_mpu_state_s *) opaque; + + if (palmte_keymap[keycode & 0x7f].row != -1) + omap_mpuio_key(cpu->mpuio, + palmte_keymap[keycode & 0x7f].row, + palmte_keymap[keycode & 0x7f].column, + !(keycode & 0x80)); +} + +static void palmte_onoff_gpios(void *opaque, int line, int level) +{ + switch (line) { + case 0: + printf("%s: current to MMC/SD card %sabled.\n", + __FUNCTION__, level ? "dis" : "en"); + break; + case 1: + printf("%s: internal speaker amplifier %s.\n", + __FUNCTION__, level ? "down" : "on"); + break; + + /* These LCD & Audio output signals have not been identified yet. */ + case 2: + case 3: + case 4: + printf("%s: LCD GPIO%i %s.\n", + __FUNCTION__, line - 1, level ? "high" : "low"); + break; + case 5: + case 6: + printf("%s: Audio GPIO%i %s.\n", + __FUNCTION__, line - 4, level ? "high" : "low"); + break; + } +} + +static void palmte_gpio_setup(struct omap_mpu_state_s *cpu) +{ + qemu_irq *misc_gpio; + + omap_mmc_handlers(cpu->mmc, + qdev_get_gpio_in(cpu->gpio, PALMTE_MMC_WP_GPIO), + qemu_irq_invert(omap_mpuio_in_get(cpu->mpuio) + [PALMTE_MMC_SWITCH_GPIO])); + + misc_gpio = qemu_allocate_irqs(palmte_onoff_gpios, cpu, 7); + qdev_connect_gpio_out(cpu->gpio, PALMTE_MMC_POWER_GPIO, misc_gpio[0]); + qdev_connect_gpio_out(cpu->gpio, PALMTE_SPEAKER_GPIO, misc_gpio[1]); + qdev_connect_gpio_out(cpu->gpio, 11, misc_gpio[2]); + qdev_connect_gpio_out(cpu->gpio, 12, misc_gpio[3]); + qdev_connect_gpio_out(cpu->gpio, 13, misc_gpio[4]); + omap_mpuio_out_set(cpu->mpuio, 1, misc_gpio[5]); + omap_mpuio_out_set(cpu->mpuio, 3, misc_gpio[6]); + + /* Reset some inputs to initial state. */ + qemu_irq_lower(qdev_get_gpio_in(cpu->gpio, PALMTE_USBDETECT_GPIO)); + qemu_irq_lower(qdev_get_gpio_in(cpu->gpio, PALMTE_USB_OR_DC_GPIO)); + qemu_irq_lower(qdev_get_gpio_in(cpu->gpio, 4)); + qemu_irq_lower(qdev_get_gpio_in(cpu->gpio, PALMTE_HEADPHONES_GPIO)); + qemu_irq_lower(omap_mpuio_in_get(cpu->mpuio)[PALMTE_DC_GPIO]); + qemu_irq_raise(omap_mpuio_in_get(cpu->mpuio)[6]); + qemu_irq_raise(omap_mpuio_in_get(cpu->mpuio)[7]); + qemu_irq_raise(omap_mpuio_in_get(cpu->mpuio)[11]); +} + +static struct arm_boot_info palmte_binfo = { + .loader_start = OMAP_EMIFF_BASE, + .ram_size = 0x02000000, + .board_id = 0x331, +}; + +static void palmte_init(MachineState *machine) +{ + const char *cpu_model = machine->cpu_model; + const char *kernel_filename = machine->kernel_filename; + const char *kernel_cmdline = machine->kernel_cmdline; + const char *initrd_filename = machine->initrd_filename; + MemoryRegion *address_space_mem = get_system_memory(); + struct omap_mpu_state_s *mpu; + int flash_size = 0x00800000; + int sdram_size = palmte_binfo.ram_size; + static uint32_t cs0val = 0xffffffff; + static uint32_t cs1val = 0x0000e1a0; + static uint32_t cs2val = 0x0000e1a0; + static uint32_t cs3val = 0xe1a0e1a0; + int rom_size, rom_loaded = 0; + MemoryRegion *flash = g_new(MemoryRegion, 1); + MemoryRegion *cs = g_new(MemoryRegion, 4); + + mpu = omap310_mpu_init(address_space_mem, sdram_size, cpu_model); + + /* External Flash (EMIFS) */ + memory_region_init_ram(flash, NULL, "palmte.flash", flash_size, + &error_abort); + vmstate_register_ram_global(flash); + memory_region_set_readonly(flash, true); + memory_region_add_subregion(address_space_mem, OMAP_CS0_BASE, flash); + + memory_region_init_io(&cs[0], NULL, &static_ops, &cs0val, "palmte-cs0", + OMAP_CS0_SIZE - flash_size); + memory_region_add_subregion(address_space_mem, OMAP_CS0_BASE + flash_size, + &cs[0]); + memory_region_init_io(&cs[1], NULL, &static_ops, &cs1val, "palmte-cs1", + OMAP_CS1_SIZE); + memory_region_add_subregion(address_space_mem, OMAP_CS1_BASE, &cs[1]); + memory_region_init_io(&cs[2], NULL, &static_ops, &cs2val, "palmte-cs2", + OMAP_CS2_SIZE); + memory_region_add_subregion(address_space_mem, OMAP_CS2_BASE, &cs[2]); + memory_region_init_io(&cs[3], NULL, &static_ops, &cs3val, "palmte-cs3", + OMAP_CS3_SIZE); + memory_region_add_subregion(address_space_mem, OMAP_CS3_BASE, &cs[3]); + + palmte_microwire_setup(mpu); + + qemu_add_kbd_event_handler(palmte_button_event, mpu); + + palmte_gpio_setup(mpu); + + /* Setup initial (reset) machine state */ + if (nb_option_roms) { + rom_size = get_image_size(option_rom[0].name); + if (rom_size > flash_size) { + fprintf(stderr, "%s: ROM image too big (%x > %x)\n", + __FUNCTION__, rom_size, flash_size); + rom_size = 0; + } + if (rom_size > 0) { + rom_size = load_image_targphys(option_rom[0].name, OMAP_CS0_BASE, + flash_size); + rom_loaded = 1; + } + if (rom_size < 0) { + fprintf(stderr, "%s: error loading '%s'\n", + __FUNCTION__, option_rom[0].name); + } + } + + if (!rom_loaded && !kernel_filename && !qtest_enabled()) { + fprintf(stderr, "Kernel or ROM image must be specified\n"); + exit(1); + } + + /* Load the kernel. */ + palmte_binfo.kernel_filename = kernel_filename; + palmte_binfo.kernel_cmdline = kernel_cmdline; + palmte_binfo.initrd_filename = initrd_filename; + arm_load_kernel(mpu->cpu, &palmte_binfo); +} + +static QEMUMachine palmte_machine = { + .name = "cheetah", + .desc = "Palm Tungsten|E aka. Cheetah PDA (OMAP310)", + .init = palmte_init, +}; + +static void palmte_machine_init(void) +{ + qemu_register_machine(&palmte_machine); +} + +machine_init(palmte_machine_init); diff --git a/qemu/hw/arm/pxa2xx.c b/qemu/hw/arm/pxa2xx.c new file mode 100644 index 000000000..ec353f79c --- /dev/null +++ b/qemu/hw/arm/pxa2xx.c @@ -0,0 +1,2354 @@ +/* + * Intel XScale PXA255/270 processor support. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski <balrog@zabor.org> + * + * This code is licensed under the GPL. + */ + +#include "hw/sysbus.h" +#include "hw/arm/pxa.h" +#include "sysemu/sysemu.h" +#include "hw/char/serial.h" +#include "hw/i2c/i2c.h" +#include "hw/ssi.h" +#include "sysemu/char.h" +#include "sysemu/block-backend.h" +#include "sysemu/blockdev.h" + +static struct { + hwaddr io_base; + int irqn; +} pxa255_serial[] = { + { 0x40100000, PXA2XX_PIC_FFUART }, + { 0x40200000, PXA2XX_PIC_BTUART }, + { 0x40700000, PXA2XX_PIC_STUART }, + { 0x41600000, PXA25X_PIC_HWUART }, + { 0, 0 } +}, pxa270_serial[] = { + { 0x40100000, PXA2XX_PIC_FFUART }, + { 0x40200000, PXA2XX_PIC_BTUART }, + { 0x40700000, PXA2XX_PIC_STUART }, + { 0, 0 } +}; + +typedef struct PXASSPDef { + hwaddr io_base; + int irqn; +} PXASSPDef; + +#if 0 +static PXASSPDef pxa250_ssp[] = { + { 0x41000000, PXA2XX_PIC_SSP }, + { 0, 0 } +}; +#endif + +static PXASSPDef pxa255_ssp[] = { + { 0x41000000, PXA2XX_PIC_SSP }, + { 0x41400000, PXA25X_PIC_NSSP }, + { 0, 0 } +}; + +#if 0 +static PXASSPDef pxa26x_ssp[] = { + { 0x41000000, PXA2XX_PIC_SSP }, + { 0x41400000, PXA25X_PIC_NSSP }, + { 0x41500000, PXA26X_PIC_ASSP }, + { 0, 0 } +}; +#endif + +static PXASSPDef pxa27x_ssp[] = { + { 0x41000000, PXA2XX_PIC_SSP }, + { 0x41700000, PXA27X_PIC_SSP2 }, + { 0x41900000, PXA2XX_PIC_SSP3 }, + { 0, 0 } +}; + +#define PMCR 0x00 /* Power Manager Control register */ +#define PSSR 0x04 /* Power Manager Sleep Status register */ +#define PSPR 0x08 /* Power Manager Scratch-Pad register */ +#define PWER 0x0c /* Power Manager Wake-Up Enable register */ +#define PRER 0x10 /* Power Manager Rising-Edge Detect Enable register */ +#define PFER 0x14 /* Power Manager Falling-Edge Detect Enable register */ +#define PEDR 0x18 /* Power Manager Edge-Detect Status register */ +#define PCFR 0x1c /* Power Manager General Configuration register */ +#define PGSR0 0x20 /* Power Manager GPIO Sleep-State register 0 */ +#define PGSR1 0x24 /* Power Manager GPIO Sleep-State register 1 */ +#define PGSR2 0x28 /* Power Manager GPIO Sleep-State register 2 */ +#define PGSR3 0x2c /* Power Manager GPIO Sleep-State register 3 */ +#define RCSR 0x30 /* Reset Controller Status register */ +#define PSLR 0x34 /* Power Manager Sleep Configuration register */ +#define PTSR 0x38 /* Power Manager Standby Configuration register */ +#define PVCR 0x40 /* Power Manager Voltage Change Control register */ +#define PUCR 0x4c /* Power Manager USIM Card Control/Status register */ +#define PKWR 0x50 /* Power Manager Keyboard Wake-Up Enable register */ +#define PKSR 0x54 /* Power Manager Keyboard Level-Detect Status */ +#define PCMD0 0x80 /* Power Manager I2C Command register File 0 */ +#define PCMD31 0xfc /* Power Manager I2C Command register File 31 */ + +static uint64_t pxa2xx_pm_read(void *opaque, hwaddr addr, + unsigned size) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + + switch (addr) { + case PMCR ... PCMD31: + if (addr & 3) + goto fail; + + return s->pm_regs[addr >> 2]; + default: + fail: + printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr); + break; + } + return 0; +} + +static void pxa2xx_pm_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + + switch (addr) { + case PMCR: + /* Clear the write-one-to-clear bits... */ + s->pm_regs[addr >> 2] &= ~(value & 0x2a); + /* ...and set the plain r/w bits */ + s->pm_regs[addr >> 2] &= ~0x15; + s->pm_regs[addr >> 2] |= value & 0x15; + break; + + case PSSR: /* Read-clean registers */ + case RCSR: + case PKSR: + s->pm_regs[addr >> 2] &= ~value; + break; + + default: /* Read-write registers */ + if (!(addr & 3)) { + s->pm_regs[addr >> 2] = value; + break; + } + + printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr); + break; + } +} + +static const MemoryRegionOps pxa2xx_pm_ops = { + .read = pxa2xx_pm_read, + .write = pxa2xx_pm_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription vmstate_pxa2xx_pm = { + .name = "pxa2xx_pm", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(pm_regs, PXA2xxState, 0x40), + VMSTATE_END_OF_LIST() + } +}; + +#define CCCR 0x00 /* Core Clock Configuration register */ +#define CKEN 0x04 /* Clock Enable register */ +#define OSCC 0x08 /* Oscillator Configuration register */ +#define CCSR 0x0c /* Core Clock Status register */ + +static uint64_t pxa2xx_cm_read(void *opaque, hwaddr addr, + unsigned size) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + + switch (addr) { + case CCCR: + case CKEN: + case OSCC: + return s->cm_regs[addr >> 2]; + + case CCSR: + return s->cm_regs[CCCR >> 2] | (3 << 28); + + default: + printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr); + break; + } + return 0; +} + +static void pxa2xx_cm_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + + switch (addr) { + case CCCR: + case CKEN: + s->cm_regs[addr >> 2] = value; + break; + + case OSCC: + s->cm_regs[addr >> 2] &= ~0x6c; + s->cm_regs[addr >> 2] |= value & 0x6e; + if ((value >> 1) & 1) /* OON */ + s->cm_regs[addr >> 2] |= 1 << 0; /* Oscillator is now stable */ + break; + + default: + printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr); + break; + } +} + +static const MemoryRegionOps pxa2xx_cm_ops = { + .read = pxa2xx_cm_read, + .write = pxa2xx_cm_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription vmstate_pxa2xx_cm = { + .name = "pxa2xx_cm", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(cm_regs, PXA2xxState, 4), + VMSTATE_UINT32(clkcfg, PXA2xxState), + VMSTATE_UINT32(pmnc, PXA2xxState), + VMSTATE_END_OF_LIST() + } +}; + +static uint64_t pxa2xx_clkcfg_read(CPUARMState *env, const ARMCPRegInfo *ri) +{ + PXA2xxState *s = (PXA2xxState *)ri->opaque; + return s->clkcfg; +} + +static void pxa2xx_clkcfg_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + PXA2xxState *s = (PXA2xxState *)ri->opaque; + s->clkcfg = value & 0xf; + if (value & 2) { + printf("%s: CPU frequency change attempt\n", __func__); + } +} + +static void pxa2xx_pwrmode_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + PXA2xxState *s = (PXA2xxState *)ri->opaque; + static const char *pwrmode[8] = { + "Normal", "Idle", "Deep-idle", "Standby", + "Sleep", "reserved (!)", "reserved (!)", "Deep-sleep", + }; + + if (value & 8) { + printf("%s: CPU voltage change attempt\n", __func__); + } + switch (value & 7) { + case 0: + /* Do nothing */ + break; + + case 1: + /* Idle */ + if (!(s->cm_regs[CCCR >> 2] & (1U << 31))) { /* CPDIS */ + cpu_interrupt(CPU(s->cpu), CPU_INTERRUPT_HALT); + break; + } + /* Fall through. */ + + case 2: + /* Deep-Idle */ + cpu_interrupt(CPU(s->cpu), CPU_INTERRUPT_HALT); + s->pm_regs[RCSR >> 2] |= 0x8; /* Set GPR */ + goto message; + + case 3: + s->cpu->env.uncached_cpsr = ARM_CPU_MODE_SVC; + s->cpu->env.daif = PSTATE_A | PSTATE_F | PSTATE_I; + s->cpu->env.cp15.sctlr_ns = 0; + s->cpu->env.cp15.cpacr_el1 = 0; + s->cpu->env.cp15.ttbr0_el[1] = 0; + s->cpu->env.cp15.dacr_ns = 0; + s->pm_regs[PSSR >> 2] |= 0x8; /* Set STS */ + s->pm_regs[RCSR >> 2] |= 0x8; /* Set GPR */ + + /* + * The scratch-pad register is almost universally used + * for storing the return address on suspend. For the + * lack of a resuming bootloader, perform a jump + * directly to that address. + */ + memset(s->cpu->env.regs, 0, 4 * 15); + s->cpu->env.regs[15] = s->pm_regs[PSPR >> 2]; + +#if 0 + buffer = 0xe59ff000; /* ldr pc, [pc, #0] */ + cpu_physical_memory_write(0, &buffer, 4); + buffer = s->pm_regs[PSPR >> 2]; + cpu_physical_memory_write(8, &buffer, 4); +#endif + + /* Suspend */ + cpu_interrupt(current_cpu, CPU_INTERRUPT_HALT); + + goto message; + + default: + message: + printf("%s: machine entered %s mode\n", __func__, + pwrmode[value & 7]); + } +} + +static uint64_t pxa2xx_cppmnc_read(CPUARMState *env, const ARMCPRegInfo *ri) +{ + PXA2xxState *s = (PXA2xxState *)ri->opaque; + return s->pmnc; +} + +static void pxa2xx_cppmnc_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + PXA2xxState *s = (PXA2xxState *)ri->opaque; + s->pmnc = value; +} + +static uint64_t pxa2xx_cpccnt_read(CPUARMState *env, const ARMCPRegInfo *ri) +{ + PXA2xxState *s = (PXA2xxState *)ri->opaque; + if (s->pmnc & 1) { + return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + } else { + return 0; + } +} + +static const ARMCPRegInfo pxa_cp_reginfo[] = { + /* cp14 crm==1: perf registers */ + { .name = "CPPMNC", .cp = 14, .crn = 0, .crm = 1, .opc1 = 0, .opc2 = 0, + .access = PL1_RW, .type = ARM_CP_IO, + .readfn = pxa2xx_cppmnc_read, .writefn = pxa2xx_cppmnc_write }, + { .name = "CPCCNT", .cp = 14, .crn = 1, .crm = 1, .opc1 = 0, .opc2 = 0, + .access = PL1_RW, .type = ARM_CP_IO, + .readfn = pxa2xx_cpccnt_read, .writefn = arm_cp_write_ignore }, + { .name = "CPINTEN", .cp = 14, .crn = 4, .crm = 1, .opc1 = 0, .opc2 = 0, + .access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 }, + { .name = "CPFLAG", .cp = 14, .crn = 5, .crm = 1, .opc1 = 0, .opc2 = 0, + .access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 }, + { .name = "CPEVTSEL", .cp = 14, .crn = 8, .crm = 1, .opc1 = 0, .opc2 = 0, + .access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 }, + /* cp14 crm==2: performance count registers */ + { .name = "CPPMN0", .cp = 14, .crn = 0, .crm = 2, .opc1 = 0, .opc2 = 0, + .access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 }, + { .name = "CPPMN1", .cp = 14, .crn = 1, .crm = 2, .opc1 = 0, .opc2 = 0, + .access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 }, + { .name = "CPPMN2", .cp = 14, .crn = 2, .crm = 2, .opc1 = 0, .opc2 = 0, + .access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 }, + { .name = "CPPMN3", .cp = 14, .crn = 2, .crm = 3, .opc1 = 0, .opc2 = 0, + .access = PL1_RW, .type = ARM_CP_CONST, .resetvalue = 0 }, + /* cp14 crn==6: CLKCFG */ + { .name = "CLKCFG", .cp = 14, .crn = 6, .crm = 0, .opc1 = 0, .opc2 = 0, + .access = PL1_RW, .type = ARM_CP_IO, + .readfn = pxa2xx_clkcfg_read, .writefn = pxa2xx_clkcfg_write }, + /* cp14 crn==7: PWRMODE */ + { .name = "PWRMODE", .cp = 14, .crn = 7, .crm = 0, .opc1 = 0, .opc2 = 0, + .access = PL1_RW, .type = ARM_CP_IO, + .readfn = arm_cp_read_zero, .writefn = pxa2xx_pwrmode_write }, + REGINFO_SENTINEL +}; + +static void pxa2xx_setup_cp14(PXA2xxState *s) +{ + define_arm_cp_regs_with_opaque(s->cpu, pxa_cp_reginfo, s); +} + +#define MDCNFG 0x00 /* SDRAM Configuration register */ +#define MDREFR 0x04 /* SDRAM Refresh Control register */ +#define MSC0 0x08 /* Static Memory Control register 0 */ +#define MSC1 0x0c /* Static Memory Control register 1 */ +#define MSC2 0x10 /* Static Memory Control register 2 */ +#define MECR 0x14 /* Expansion Memory Bus Config register */ +#define SXCNFG 0x1c /* Synchronous Static Memory Config register */ +#define MCMEM0 0x28 /* PC Card Memory Socket 0 Timing register */ +#define MCMEM1 0x2c /* PC Card Memory Socket 1 Timing register */ +#define MCATT0 0x30 /* PC Card Attribute Socket 0 register */ +#define MCATT1 0x34 /* PC Card Attribute Socket 1 register */ +#define MCIO0 0x38 /* PC Card I/O Socket 0 Timing register */ +#define MCIO1 0x3c /* PC Card I/O Socket 1 Timing register */ +#define MDMRS 0x40 /* SDRAM Mode Register Set Config register */ +#define BOOT_DEF 0x44 /* Boot-time Default Configuration register */ +#define ARB_CNTL 0x48 /* Arbiter Control register */ +#define BSCNTR0 0x4c /* Memory Buffer Strength Control register 0 */ +#define BSCNTR1 0x50 /* Memory Buffer Strength Control register 1 */ +#define LCDBSCNTR 0x54 /* LCD Buffer Strength Control register */ +#define MDMRSLP 0x58 /* Low Power SDRAM Mode Set Config register */ +#define BSCNTR2 0x5c /* Memory Buffer Strength Control register 2 */ +#define BSCNTR3 0x60 /* Memory Buffer Strength Control register 3 */ +#define SA1110 0x64 /* SA-1110 Memory Compatibility register */ + +static uint64_t pxa2xx_mm_read(void *opaque, hwaddr addr, + unsigned size) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + + switch (addr) { + case MDCNFG ... SA1110: + if ((addr & 3) == 0) + return s->mm_regs[addr >> 2]; + + default: + printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr); + break; + } + return 0; +} + +static void pxa2xx_mm_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + + switch (addr) { + case MDCNFG ... SA1110: + if ((addr & 3) == 0) { + s->mm_regs[addr >> 2] = value; + break; + } + + default: + printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr); + break; + } +} + +static const MemoryRegionOps pxa2xx_mm_ops = { + .read = pxa2xx_mm_read, + .write = pxa2xx_mm_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription vmstate_pxa2xx_mm = { + .name = "pxa2xx_mm", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(mm_regs, PXA2xxState, 0x1a), + VMSTATE_END_OF_LIST() + } +}; + +#define TYPE_PXA2XX_SSP "pxa2xx-ssp" +#define PXA2XX_SSP(obj) \ + OBJECT_CHECK(PXA2xxSSPState, (obj), TYPE_PXA2XX_SSP) + +/* Synchronous Serial Ports */ +typedef struct { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion iomem; + qemu_irq irq; + uint32_t enable; + SSIBus *bus; + + uint32_t sscr[2]; + uint32_t sspsp; + uint32_t ssto; + uint32_t ssitr; + uint32_t sssr; + uint8_t sstsa; + uint8_t ssrsa; + uint8_t ssacd; + + uint32_t rx_fifo[16]; + uint32_t rx_level; + uint32_t rx_start; +} PXA2xxSSPState; + +static bool pxa2xx_ssp_vmstate_validate(void *opaque, int version_id) +{ + PXA2xxSSPState *s = opaque; + + return s->rx_start < sizeof(s->rx_fifo); +} + +static const VMStateDescription vmstate_pxa2xx_ssp = { + .name = "pxa2xx-ssp", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(enable, PXA2xxSSPState), + VMSTATE_UINT32_ARRAY(sscr, PXA2xxSSPState, 2), + VMSTATE_UINT32(sspsp, PXA2xxSSPState), + VMSTATE_UINT32(ssto, PXA2xxSSPState), + VMSTATE_UINT32(ssitr, PXA2xxSSPState), + VMSTATE_UINT32(sssr, PXA2xxSSPState), + VMSTATE_UINT8(sstsa, PXA2xxSSPState), + VMSTATE_UINT8(ssrsa, PXA2xxSSPState), + VMSTATE_UINT8(ssacd, PXA2xxSSPState), + VMSTATE_UINT32(rx_level, PXA2xxSSPState), + VMSTATE_UINT32(rx_start, PXA2xxSSPState), + VMSTATE_VALIDATE("fifo is 16 bytes", pxa2xx_ssp_vmstate_validate), + VMSTATE_UINT32_ARRAY(rx_fifo, PXA2xxSSPState, 16), + VMSTATE_END_OF_LIST() + } +}; + +#define SSCR0 0x00 /* SSP Control register 0 */ +#define SSCR1 0x04 /* SSP Control register 1 */ +#define SSSR 0x08 /* SSP Status register */ +#define SSITR 0x0c /* SSP Interrupt Test register */ +#define SSDR 0x10 /* SSP Data register */ +#define SSTO 0x28 /* SSP Time-Out register */ +#define SSPSP 0x2c /* SSP Programmable Serial Protocol register */ +#define SSTSA 0x30 /* SSP TX Time Slot Active register */ +#define SSRSA 0x34 /* SSP RX Time Slot Active register */ +#define SSTSS 0x38 /* SSP Time Slot Status register */ +#define SSACD 0x3c /* SSP Audio Clock Divider register */ + +/* Bitfields for above registers */ +#define SSCR0_SPI(x) (((x) & 0x30) == 0x00) +#define SSCR0_SSP(x) (((x) & 0x30) == 0x10) +#define SSCR0_UWIRE(x) (((x) & 0x30) == 0x20) +#define SSCR0_PSP(x) (((x) & 0x30) == 0x30) +#define SSCR0_SSE (1 << 7) +#define SSCR0_RIM (1 << 22) +#define SSCR0_TIM (1 << 23) +#define SSCR0_MOD (1U << 31) +#define SSCR0_DSS(x) (((((x) >> 16) & 0x10) | ((x) & 0xf)) + 1) +#define SSCR1_RIE (1 << 0) +#define SSCR1_TIE (1 << 1) +#define SSCR1_LBM (1 << 2) +#define SSCR1_MWDS (1 << 5) +#define SSCR1_TFT(x) ((((x) >> 6) & 0xf) + 1) +#define SSCR1_RFT(x) ((((x) >> 10) & 0xf) + 1) +#define SSCR1_EFWR (1 << 14) +#define SSCR1_PINTE (1 << 18) +#define SSCR1_TINTE (1 << 19) +#define SSCR1_RSRE (1 << 20) +#define SSCR1_TSRE (1 << 21) +#define SSCR1_EBCEI (1 << 29) +#define SSITR_INT (7 << 5) +#define SSSR_TNF (1 << 2) +#define SSSR_RNE (1 << 3) +#define SSSR_TFS (1 << 5) +#define SSSR_RFS (1 << 6) +#define SSSR_ROR (1 << 7) +#define SSSR_PINT (1 << 18) +#define SSSR_TINT (1 << 19) +#define SSSR_EOC (1 << 20) +#define SSSR_TUR (1 << 21) +#define SSSR_BCE (1 << 23) +#define SSSR_RW 0x00bc0080 + +static void pxa2xx_ssp_int_update(PXA2xxSSPState *s) +{ + int level = 0; + + level |= s->ssitr & SSITR_INT; + level |= (s->sssr & SSSR_BCE) && (s->sscr[1] & SSCR1_EBCEI); + level |= (s->sssr & SSSR_TUR) && !(s->sscr[0] & SSCR0_TIM); + level |= (s->sssr & SSSR_EOC) && (s->sssr & (SSSR_TINT | SSSR_PINT)); + level |= (s->sssr & SSSR_TINT) && (s->sscr[1] & SSCR1_TINTE); + level |= (s->sssr & SSSR_PINT) && (s->sscr[1] & SSCR1_PINTE); + level |= (s->sssr & SSSR_ROR) && !(s->sscr[0] & SSCR0_RIM); + level |= (s->sssr & SSSR_RFS) && (s->sscr[1] & SSCR1_RIE); + level |= (s->sssr & SSSR_TFS) && (s->sscr[1] & SSCR1_TIE); + qemu_set_irq(s->irq, !!level); +} + +static void pxa2xx_ssp_fifo_update(PXA2xxSSPState *s) +{ + s->sssr &= ~(0xf << 12); /* Clear RFL */ + s->sssr &= ~(0xf << 8); /* Clear TFL */ + s->sssr &= ~SSSR_TFS; + s->sssr &= ~SSSR_TNF; + if (s->enable) { + s->sssr |= ((s->rx_level - 1) & 0xf) << 12; + if (s->rx_level >= SSCR1_RFT(s->sscr[1])) + s->sssr |= SSSR_RFS; + else + s->sssr &= ~SSSR_RFS; + if (s->rx_level) + s->sssr |= SSSR_RNE; + else + s->sssr &= ~SSSR_RNE; + /* TX FIFO is never filled, so it is always in underrun + condition if SSP is enabled */ + s->sssr |= SSSR_TFS; + s->sssr |= SSSR_TNF; + } + + pxa2xx_ssp_int_update(s); +} + +static uint64_t pxa2xx_ssp_read(void *opaque, hwaddr addr, + unsigned size) +{ + PXA2xxSSPState *s = (PXA2xxSSPState *) opaque; + uint32_t retval; + + switch (addr) { + case SSCR0: + return s->sscr[0]; + case SSCR1: + return s->sscr[1]; + case SSPSP: + return s->sspsp; + case SSTO: + return s->ssto; + case SSITR: + return s->ssitr; + case SSSR: + return s->sssr | s->ssitr; + case SSDR: + if (!s->enable) + return 0xffffffff; + if (s->rx_level < 1) { + printf("%s: SSP Rx Underrun\n", __FUNCTION__); + return 0xffffffff; + } + s->rx_level --; + retval = s->rx_fifo[s->rx_start ++]; + s->rx_start &= 0xf; + pxa2xx_ssp_fifo_update(s); + return retval; + case SSTSA: + return s->sstsa; + case SSRSA: + return s->ssrsa; + case SSTSS: + return 0; + case SSACD: + return s->ssacd; + default: + printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr); + break; + } + return 0; +} + +static void pxa2xx_ssp_write(void *opaque, hwaddr addr, + uint64_t value64, unsigned size) +{ + PXA2xxSSPState *s = (PXA2xxSSPState *) opaque; + uint32_t value = value64; + + switch (addr) { + case SSCR0: + s->sscr[0] = value & 0xc7ffffff; + s->enable = value & SSCR0_SSE; + if (value & SSCR0_MOD) + printf("%s: Attempt to use network mode\n", __FUNCTION__); + if (s->enable && SSCR0_DSS(value) < 4) + printf("%s: Wrong data size: %i bits\n", __FUNCTION__, + SSCR0_DSS(value)); + if (!(value & SSCR0_SSE)) { + s->sssr = 0; + s->ssitr = 0; + s->rx_level = 0; + } + pxa2xx_ssp_fifo_update(s); + break; + + case SSCR1: + s->sscr[1] = value; + if (value & (SSCR1_LBM | SSCR1_EFWR)) + printf("%s: Attempt to use SSP test mode\n", __FUNCTION__); + pxa2xx_ssp_fifo_update(s); + break; + + case SSPSP: + s->sspsp = value; + break; + + case SSTO: + s->ssto = value; + break; + + case SSITR: + s->ssitr = value & SSITR_INT; + pxa2xx_ssp_int_update(s); + break; + + case SSSR: + s->sssr &= ~(value & SSSR_RW); + pxa2xx_ssp_int_update(s); + break; + + case SSDR: + if (SSCR0_UWIRE(s->sscr[0])) { + if (s->sscr[1] & SSCR1_MWDS) + value &= 0xffff; + else + value &= 0xff; + } else + /* Note how 32bits overflow does no harm here */ + value &= (1 << SSCR0_DSS(s->sscr[0])) - 1; + + /* Data goes from here to the Tx FIFO and is shifted out from + * there directly to the slave, no need to buffer it. + */ + if (s->enable) { + uint32_t readval; + readval = ssi_transfer(s->bus, value); + if (s->rx_level < 0x10) { + s->rx_fifo[(s->rx_start + s->rx_level ++) & 0xf] = readval; + } else { + s->sssr |= SSSR_ROR; + } + } + pxa2xx_ssp_fifo_update(s); + break; + + case SSTSA: + s->sstsa = value; + break; + + case SSRSA: + s->ssrsa = value; + break; + + case SSACD: + s->ssacd = value; + break; + + default: + printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr); + break; + } +} + +static const MemoryRegionOps pxa2xx_ssp_ops = { + .read = pxa2xx_ssp_read, + .write = pxa2xx_ssp_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void pxa2xx_ssp_reset(DeviceState *d) +{ + PXA2xxSSPState *s = PXA2XX_SSP(d); + + s->enable = 0; + s->sscr[0] = s->sscr[1] = 0; + s->sspsp = 0; + s->ssto = 0; + s->ssitr = 0; + s->sssr = 0; + s->sstsa = 0; + s->ssrsa = 0; + s->ssacd = 0; + s->rx_start = s->rx_level = 0; +} + +static int pxa2xx_ssp_init(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + PXA2xxSSPState *s = PXA2XX_SSP(dev); + + sysbus_init_irq(sbd, &s->irq); + + memory_region_init_io(&s->iomem, OBJECT(s), &pxa2xx_ssp_ops, s, + "pxa2xx-ssp", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + + s->bus = ssi_create_bus(dev, "ssi"); + return 0; +} + +/* Real-Time Clock */ +#define RCNR 0x00 /* RTC Counter register */ +#define RTAR 0x04 /* RTC Alarm register */ +#define RTSR 0x08 /* RTC Status register */ +#define RTTR 0x0c /* RTC Timer Trim register */ +#define RDCR 0x10 /* RTC Day Counter register */ +#define RYCR 0x14 /* RTC Year Counter register */ +#define RDAR1 0x18 /* RTC Wristwatch Day Alarm register 1 */ +#define RYAR1 0x1c /* RTC Wristwatch Year Alarm register 1 */ +#define RDAR2 0x20 /* RTC Wristwatch Day Alarm register 2 */ +#define RYAR2 0x24 /* RTC Wristwatch Year Alarm register 2 */ +#define SWCR 0x28 /* RTC Stopwatch Counter register */ +#define SWAR1 0x2c /* RTC Stopwatch Alarm register 1 */ +#define SWAR2 0x30 /* RTC Stopwatch Alarm register 2 */ +#define RTCPICR 0x34 /* RTC Periodic Interrupt Counter register */ +#define PIAR 0x38 /* RTC Periodic Interrupt Alarm register */ + +#define TYPE_PXA2XX_RTC "pxa2xx_rtc" +#define PXA2XX_RTC(obj) \ + OBJECT_CHECK(PXA2xxRTCState, (obj), TYPE_PXA2XX_RTC) + +typedef struct { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion iomem; + uint32_t rttr; + uint32_t rtsr; + uint32_t rtar; + uint32_t rdar1; + uint32_t rdar2; + uint32_t ryar1; + uint32_t ryar2; + uint32_t swar1; + uint32_t swar2; + uint32_t piar; + uint32_t last_rcnr; + uint32_t last_rdcr; + uint32_t last_rycr; + uint32_t last_swcr; + uint32_t last_rtcpicr; + int64_t last_hz; + int64_t last_sw; + int64_t last_pi; + QEMUTimer *rtc_hz; + QEMUTimer *rtc_rdal1; + QEMUTimer *rtc_rdal2; + QEMUTimer *rtc_swal1; + QEMUTimer *rtc_swal2; + QEMUTimer *rtc_pi; + qemu_irq rtc_irq; +} PXA2xxRTCState; + +static inline void pxa2xx_rtc_int_update(PXA2xxRTCState *s) +{ + qemu_set_irq(s->rtc_irq, !!(s->rtsr & 0x2553)); +} + +static void pxa2xx_rtc_hzupdate(PXA2xxRTCState *s) +{ + int64_t rt = qemu_clock_get_ms(rtc_clock); + s->last_rcnr += ((rt - s->last_hz) << 15) / + (1000 * ((s->rttr & 0xffff) + 1)); + s->last_rdcr += ((rt - s->last_hz) << 15) / + (1000 * ((s->rttr & 0xffff) + 1)); + s->last_hz = rt; +} + +static void pxa2xx_rtc_swupdate(PXA2xxRTCState *s) +{ + int64_t rt = qemu_clock_get_ms(rtc_clock); + if (s->rtsr & (1 << 12)) + s->last_swcr += (rt - s->last_sw) / 10; + s->last_sw = rt; +} + +static void pxa2xx_rtc_piupdate(PXA2xxRTCState *s) +{ + int64_t rt = qemu_clock_get_ms(rtc_clock); + if (s->rtsr & (1 << 15)) + s->last_swcr += rt - s->last_pi; + s->last_pi = rt; +} + +static inline void pxa2xx_rtc_alarm_update(PXA2xxRTCState *s, + uint32_t rtsr) +{ + if ((rtsr & (1 << 2)) && !(rtsr & (1 << 0))) + timer_mod(s->rtc_hz, s->last_hz + + (((s->rtar - s->last_rcnr) * 1000 * + ((s->rttr & 0xffff) + 1)) >> 15)); + else + timer_del(s->rtc_hz); + + if ((rtsr & (1 << 5)) && !(rtsr & (1 << 4))) + timer_mod(s->rtc_rdal1, s->last_hz + + (((s->rdar1 - s->last_rdcr) * 1000 * + ((s->rttr & 0xffff) + 1)) >> 15)); /* TODO: fixup */ + else + timer_del(s->rtc_rdal1); + + if ((rtsr & (1 << 7)) && !(rtsr & (1 << 6))) + timer_mod(s->rtc_rdal2, s->last_hz + + (((s->rdar2 - s->last_rdcr) * 1000 * + ((s->rttr & 0xffff) + 1)) >> 15)); /* TODO: fixup */ + else + timer_del(s->rtc_rdal2); + + if ((rtsr & 0x1200) == 0x1200 && !(rtsr & (1 << 8))) + timer_mod(s->rtc_swal1, s->last_sw + + (s->swar1 - s->last_swcr) * 10); /* TODO: fixup */ + else + timer_del(s->rtc_swal1); + + if ((rtsr & 0x1800) == 0x1800 && !(rtsr & (1 << 10))) + timer_mod(s->rtc_swal2, s->last_sw + + (s->swar2 - s->last_swcr) * 10); /* TODO: fixup */ + else + timer_del(s->rtc_swal2); + + if ((rtsr & 0xc000) == 0xc000 && !(rtsr & (1 << 13))) + timer_mod(s->rtc_pi, s->last_pi + + (s->piar & 0xffff) - s->last_rtcpicr); + else + timer_del(s->rtc_pi); +} + +static inline void pxa2xx_rtc_hz_tick(void *opaque) +{ + PXA2xxRTCState *s = (PXA2xxRTCState *) opaque; + s->rtsr |= (1 << 0); + pxa2xx_rtc_alarm_update(s, s->rtsr); + pxa2xx_rtc_int_update(s); +} + +static inline void pxa2xx_rtc_rdal1_tick(void *opaque) +{ + PXA2xxRTCState *s = (PXA2xxRTCState *) opaque; + s->rtsr |= (1 << 4); + pxa2xx_rtc_alarm_update(s, s->rtsr); + pxa2xx_rtc_int_update(s); +} + +static inline void pxa2xx_rtc_rdal2_tick(void *opaque) +{ + PXA2xxRTCState *s = (PXA2xxRTCState *) opaque; + s->rtsr |= (1 << 6); + pxa2xx_rtc_alarm_update(s, s->rtsr); + pxa2xx_rtc_int_update(s); +} + +static inline void pxa2xx_rtc_swal1_tick(void *opaque) +{ + PXA2xxRTCState *s = (PXA2xxRTCState *) opaque; + s->rtsr |= (1 << 8); + pxa2xx_rtc_alarm_update(s, s->rtsr); + pxa2xx_rtc_int_update(s); +} + +static inline void pxa2xx_rtc_swal2_tick(void *opaque) +{ + PXA2xxRTCState *s = (PXA2xxRTCState *) opaque; + s->rtsr |= (1 << 10); + pxa2xx_rtc_alarm_update(s, s->rtsr); + pxa2xx_rtc_int_update(s); +} + +static inline void pxa2xx_rtc_pi_tick(void *opaque) +{ + PXA2xxRTCState *s = (PXA2xxRTCState *) opaque; + s->rtsr |= (1 << 13); + pxa2xx_rtc_piupdate(s); + s->last_rtcpicr = 0; + pxa2xx_rtc_alarm_update(s, s->rtsr); + pxa2xx_rtc_int_update(s); +} + +static uint64_t pxa2xx_rtc_read(void *opaque, hwaddr addr, + unsigned size) +{ + PXA2xxRTCState *s = (PXA2xxRTCState *) opaque; + + switch (addr) { + case RTTR: + return s->rttr; + case RTSR: + return s->rtsr; + case RTAR: + return s->rtar; + case RDAR1: + return s->rdar1; + case RDAR2: + return s->rdar2; + case RYAR1: + return s->ryar1; + case RYAR2: + return s->ryar2; + case SWAR1: + return s->swar1; + case SWAR2: + return s->swar2; + case PIAR: + return s->piar; + case RCNR: + return s->last_rcnr + + ((qemu_clock_get_ms(rtc_clock) - s->last_hz) << 15) / + (1000 * ((s->rttr & 0xffff) + 1)); + case RDCR: + return s->last_rdcr + + ((qemu_clock_get_ms(rtc_clock) - s->last_hz) << 15) / + (1000 * ((s->rttr & 0xffff) + 1)); + case RYCR: + return s->last_rycr; + case SWCR: + if (s->rtsr & (1 << 12)) + return s->last_swcr + + (qemu_clock_get_ms(rtc_clock) - s->last_sw) / 10; + else + return s->last_swcr; + default: + printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr); + break; + } + return 0; +} + +static void pxa2xx_rtc_write(void *opaque, hwaddr addr, + uint64_t value64, unsigned size) +{ + PXA2xxRTCState *s = (PXA2xxRTCState *) opaque; + uint32_t value = value64; + + switch (addr) { + case RTTR: + if (!(s->rttr & (1U << 31))) { + pxa2xx_rtc_hzupdate(s); + s->rttr = value; + pxa2xx_rtc_alarm_update(s, s->rtsr); + } + break; + + case RTSR: + if ((s->rtsr ^ value) & (1 << 15)) + pxa2xx_rtc_piupdate(s); + + if ((s->rtsr ^ value) & (1 << 12)) + pxa2xx_rtc_swupdate(s); + + if (((s->rtsr ^ value) & 0x4aac) | (value & ~0xdaac)) + pxa2xx_rtc_alarm_update(s, value); + + s->rtsr = (value & 0xdaac) | (s->rtsr & ~(value & ~0xdaac)); + pxa2xx_rtc_int_update(s); + break; + + case RTAR: + s->rtar = value; + pxa2xx_rtc_alarm_update(s, s->rtsr); + break; + + case RDAR1: + s->rdar1 = value; + pxa2xx_rtc_alarm_update(s, s->rtsr); + break; + + case RDAR2: + s->rdar2 = value; + pxa2xx_rtc_alarm_update(s, s->rtsr); + break; + + case RYAR1: + s->ryar1 = value; + pxa2xx_rtc_alarm_update(s, s->rtsr); + break; + + case RYAR2: + s->ryar2 = value; + pxa2xx_rtc_alarm_update(s, s->rtsr); + break; + + case SWAR1: + pxa2xx_rtc_swupdate(s); + s->swar1 = value; + s->last_swcr = 0; + pxa2xx_rtc_alarm_update(s, s->rtsr); + break; + + case SWAR2: + s->swar2 = value; + pxa2xx_rtc_alarm_update(s, s->rtsr); + break; + + case PIAR: + s->piar = value; + pxa2xx_rtc_alarm_update(s, s->rtsr); + break; + + case RCNR: + pxa2xx_rtc_hzupdate(s); + s->last_rcnr = value; + pxa2xx_rtc_alarm_update(s, s->rtsr); + break; + + case RDCR: + pxa2xx_rtc_hzupdate(s); + s->last_rdcr = value; + pxa2xx_rtc_alarm_update(s, s->rtsr); + break; + + case RYCR: + s->last_rycr = value; + break; + + case SWCR: + pxa2xx_rtc_swupdate(s); + s->last_swcr = value; + pxa2xx_rtc_alarm_update(s, s->rtsr); + break; + + case RTCPICR: + pxa2xx_rtc_piupdate(s); + s->last_rtcpicr = value & 0xffff; + pxa2xx_rtc_alarm_update(s, s->rtsr); + break; + + default: + printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr); + } +} + +static const MemoryRegionOps pxa2xx_rtc_ops = { + .read = pxa2xx_rtc_read, + .write = pxa2xx_rtc_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int pxa2xx_rtc_init(SysBusDevice *dev) +{ + PXA2xxRTCState *s = PXA2XX_RTC(dev); + struct tm tm; + int wom; + + s->rttr = 0x7fff; + s->rtsr = 0; + + qemu_get_timedate(&tm, 0); + wom = ((tm.tm_mday - 1) / 7) + 1; + + s->last_rcnr = (uint32_t) mktimegm(&tm); + s->last_rdcr = (wom << 20) | ((tm.tm_wday + 1) << 17) | + (tm.tm_hour << 12) | (tm.tm_min << 6) | tm.tm_sec; + s->last_rycr = ((tm.tm_year + 1900) << 9) | + ((tm.tm_mon + 1) << 5) | tm.tm_mday; + s->last_swcr = (tm.tm_hour << 19) | + (tm.tm_min << 13) | (tm.tm_sec << 7); + s->last_rtcpicr = 0; + s->last_hz = s->last_sw = s->last_pi = qemu_clock_get_ms(rtc_clock); + + s->rtc_hz = timer_new_ms(rtc_clock, pxa2xx_rtc_hz_tick, s); + s->rtc_rdal1 = timer_new_ms(rtc_clock, pxa2xx_rtc_rdal1_tick, s); + s->rtc_rdal2 = timer_new_ms(rtc_clock, pxa2xx_rtc_rdal2_tick, s); + s->rtc_swal1 = timer_new_ms(rtc_clock, pxa2xx_rtc_swal1_tick, s); + s->rtc_swal2 = timer_new_ms(rtc_clock, pxa2xx_rtc_swal2_tick, s); + s->rtc_pi = timer_new_ms(rtc_clock, pxa2xx_rtc_pi_tick, s); + + sysbus_init_irq(dev, &s->rtc_irq); + + memory_region_init_io(&s->iomem, OBJECT(s), &pxa2xx_rtc_ops, s, + "pxa2xx-rtc", 0x10000); + sysbus_init_mmio(dev, &s->iomem); + + return 0; +} + +static void pxa2xx_rtc_pre_save(void *opaque) +{ + PXA2xxRTCState *s = (PXA2xxRTCState *) opaque; + + pxa2xx_rtc_hzupdate(s); + pxa2xx_rtc_piupdate(s); + pxa2xx_rtc_swupdate(s); +} + +static int pxa2xx_rtc_post_load(void *opaque, int version_id) +{ + PXA2xxRTCState *s = (PXA2xxRTCState *) opaque; + + pxa2xx_rtc_alarm_update(s, s->rtsr); + + return 0; +} + +static const VMStateDescription vmstate_pxa2xx_rtc_regs = { + .name = "pxa2xx_rtc", + .version_id = 0, + .minimum_version_id = 0, + .pre_save = pxa2xx_rtc_pre_save, + .post_load = pxa2xx_rtc_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32(rttr, PXA2xxRTCState), + VMSTATE_UINT32(rtsr, PXA2xxRTCState), + VMSTATE_UINT32(rtar, PXA2xxRTCState), + VMSTATE_UINT32(rdar1, PXA2xxRTCState), + VMSTATE_UINT32(rdar2, PXA2xxRTCState), + VMSTATE_UINT32(ryar1, PXA2xxRTCState), + VMSTATE_UINT32(ryar2, PXA2xxRTCState), + VMSTATE_UINT32(swar1, PXA2xxRTCState), + VMSTATE_UINT32(swar2, PXA2xxRTCState), + VMSTATE_UINT32(piar, PXA2xxRTCState), + VMSTATE_UINT32(last_rcnr, PXA2xxRTCState), + VMSTATE_UINT32(last_rdcr, PXA2xxRTCState), + VMSTATE_UINT32(last_rycr, PXA2xxRTCState), + VMSTATE_UINT32(last_swcr, PXA2xxRTCState), + VMSTATE_UINT32(last_rtcpicr, PXA2xxRTCState), + VMSTATE_INT64(last_hz, PXA2xxRTCState), + VMSTATE_INT64(last_sw, PXA2xxRTCState), + VMSTATE_INT64(last_pi, PXA2xxRTCState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void pxa2xx_rtc_sysbus_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = pxa2xx_rtc_init; + dc->desc = "PXA2xx RTC Controller"; + dc->vmsd = &vmstate_pxa2xx_rtc_regs; +} + +static const TypeInfo pxa2xx_rtc_sysbus_info = { + .name = TYPE_PXA2XX_RTC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PXA2xxRTCState), + .class_init = pxa2xx_rtc_sysbus_class_init, +}; + +/* I2C Interface */ + +#define TYPE_PXA2XX_I2C_SLAVE "pxa2xx-i2c-slave" +#define PXA2XX_I2C_SLAVE(obj) \ + OBJECT_CHECK(PXA2xxI2CSlaveState, (obj), TYPE_PXA2XX_I2C_SLAVE) + +typedef struct PXA2xxI2CSlaveState { + I2CSlave parent_obj; + + PXA2xxI2CState *host; +} PXA2xxI2CSlaveState; + +#define TYPE_PXA2XX_I2C "pxa2xx_i2c" +#define PXA2XX_I2C(obj) \ + OBJECT_CHECK(PXA2xxI2CState, (obj), TYPE_PXA2XX_I2C) + +struct PXA2xxI2CState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion iomem; + PXA2xxI2CSlaveState *slave; + I2CBus *bus; + qemu_irq irq; + uint32_t offset; + uint32_t region_size; + + uint16_t control; + uint16_t status; + uint8_t ibmr; + uint8_t data; +}; + +#define IBMR 0x80 /* I2C Bus Monitor register */ +#define IDBR 0x88 /* I2C Data Buffer register */ +#define ICR 0x90 /* I2C Control register */ +#define ISR 0x98 /* I2C Status register */ +#define ISAR 0xa0 /* I2C Slave Address register */ + +static void pxa2xx_i2c_update(PXA2xxI2CState *s) +{ + uint16_t level = 0; + level |= s->status & s->control & (1 << 10); /* BED */ + level |= (s->status & (1 << 7)) && (s->control & (1 << 9)); /* IRF */ + level |= (s->status & (1 << 6)) && (s->control & (1 << 8)); /* ITE */ + level |= s->status & (1 << 9); /* SAD */ + qemu_set_irq(s->irq, !!level); +} + +/* These are only stubs now. */ +static void pxa2xx_i2c_event(I2CSlave *i2c, enum i2c_event event) +{ + PXA2xxI2CSlaveState *slave = PXA2XX_I2C_SLAVE(i2c); + PXA2xxI2CState *s = slave->host; + + switch (event) { + case I2C_START_SEND: + s->status |= (1 << 9); /* set SAD */ + s->status &= ~(1 << 0); /* clear RWM */ + break; + case I2C_START_RECV: + s->status |= (1 << 9); /* set SAD */ + s->status |= 1 << 0; /* set RWM */ + break; + case I2C_FINISH: + s->status |= (1 << 4); /* set SSD */ + break; + case I2C_NACK: + s->status |= 1 << 1; /* set ACKNAK */ + break; + } + pxa2xx_i2c_update(s); +} + +static int pxa2xx_i2c_rx(I2CSlave *i2c) +{ + PXA2xxI2CSlaveState *slave = PXA2XX_I2C_SLAVE(i2c); + PXA2xxI2CState *s = slave->host; + + if ((s->control & (1 << 14)) || !(s->control & (1 << 6))) { + return 0; + } + + if (s->status & (1 << 0)) { /* RWM */ + s->status |= 1 << 6; /* set ITE */ + } + pxa2xx_i2c_update(s); + + return s->data; +} + +static int pxa2xx_i2c_tx(I2CSlave *i2c, uint8_t data) +{ + PXA2xxI2CSlaveState *slave = PXA2XX_I2C_SLAVE(i2c); + PXA2xxI2CState *s = slave->host; + + if ((s->control & (1 << 14)) || !(s->control & (1 << 6))) { + return 1; + } + + if (!(s->status & (1 << 0))) { /* RWM */ + s->status |= 1 << 7; /* set IRF */ + s->data = data; + } + pxa2xx_i2c_update(s); + + return 1; +} + +static uint64_t pxa2xx_i2c_read(void *opaque, hwaddr addr, + unsigned size) +{ + PXA2xxI2CState *s = (PXA2xxI2CState *) opaque; + I2CSlave *slave; + + addr -= s->offset; + switch (addr) { + case ICR: + return s->control; + case ISR: + return s->status | (i2c_bus_busy(s->bus) << 2); + case ISAR: + slave = I2C_SLAVE(s->slave); + return slave->address; + case IDBR: + return s->data; + case IBMR: + if (s->status & (1 << 2)) + s->ibmr ^= 3; /* Fake SCL and SDA pin changes */ + else + s->ibmr = 0; + return s->ibmr; + default: + printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr); + break; + } + return 0; +} + +static void pxa2xx_i2c_write(void *opaque, hwaddr addr, + uint64_t value64, unsigned size) +{ + PXA2xxI2CState *s = (PXA2xxI2CState *) opaque; + uint32_t value = value64; + int ack; + + addr -= s->offset; + switch (addr) { + case ICR: + s->control = value & 0xfff7; + if ((value & (1 << 3)) && (value & (1 << 6))) { /* TB and IUE */ + /* TODO: slave mode */ + if (value & (1 << 0)) { /* START condition */ + if (s->data & 1) + s->status |= 1 << 0; /* set RWM */ + else + s->status &= ~(1 << 0); /* clear RWM */ + ack = !i2c_start_transfer(s->bus, s->data >> 1, s->data & 1); + } else { + if (s->status & (1 << 0)) { /* RWM */ + s->data = i2c_recv(s->bus); + if (value & (1 << 2)) /* ACKNAK */ + i2c_nack(s->bus); + ack = 1; + } else + ack = !i2c_send(s->bus, s->data); + } + + if (value & (1 << 1)) /* STOP condition */ + i2c_end_transfer(s->bus); + + if (ack) { + if (value & (1 << 0)) /* START condition */ + s->status |= 1 << 6; /* set ITE */ + else + if (s->status & (1 << 0)) /* RWM */ + s->status |= 1 << 7; /* set IRF */ + else + s->status |= 1 << 6; /* set ITE */ + s->status &= ~(1 << 1); /* clear ACKNAK */ + } else { + s->status |= 1 << 6; /* set ITE */ + s->status |= 1 << 10; /* set BED */ + s->status |= 1 << 1; /* set ACKNAK */ + } + } + if (!(value & (1 << 3)) && (value & (1 << 6))) /* !TB and IUE */ + if (value & (1 << 4)) /* MA */ + i2c_end_transfer(s->bus); + pxa2xx_i2c_update(s); + break; + + case ISR: + s->status &= ~(value & 0x07f0); + pxa2xx_i2c_update(s); + break; + + case ISAR: + i2c_set_slave_address(I2C_SLAVE(s->slave), value & 0x7f); + break; + + case IDBR: + s->data = value & 0xff; + break; + + default: + printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr); + } +} + +static const MemoryRegionOps pxa2xx_i2c_ops = { + .read = pxa2xx_i2c_read, + .write = pxa2xx_i2c_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription vmstate_pxa2xx_i2c_slave = { + .name = "pxa2xx_i2c_slave", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_I2C_SLAVE(parent_obj, PXA2xxI2CSlaveState), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_pxa2xx_i2c = { + .name = "pxa2xx_i2c", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT16(control, PXA2xxI2CState), + VMSTATE_UINT16(status, PXA2xxI2CState), + VMSTATE_UINT8(ibmr, PXA2xxI2CState), + VMSTATE_UINT8(data, PXA2xxI2CState), + VMSTATE_STRUCT_POINTER(slave, PXA2xxI2CState, + vmstate_pxa2xx_i2c_slave, PXA2xxI2CSlaveState), + VMSTATE_END_OF_LIST() + } +}; + +static int pxa2xx_i2c_slave_init(I2CSlave *i2c) +{ + /* Nothing to do. */ + return 0; +} + +static void pxa2xx_i2c_slave_class_init(ObjectClass *klass, void *data) +{ + I2CSlaveClass *k = I2C_SLAVE_CLASS(klass); + + k->init = pxa2xx_i2c_slave_init; + k->event = pxa2xx_i2c_event; + k->recv = pxa2xx_i2c_rx; + k->send = pxa2xx_i2c_tx; +} + +static const TypeInfo pxa2xx_i2c_slave_info = { + .name = TYPE_PXA2XX_I2C_SLAVE, + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(PXA2xxI2CSlaveState), + .class_init = pxa2xx_i2c_slave_class_init, +}; + +PXA2xxI2CState *pxa2xx_i2c_init(hwaddr base, + qemu_irq irq, uint32_t region_size) +{ + DeviceState *dev; + SysBusDevice *i2c_dev; + PXA2xxI2CState *s; + I2CBus *i2cbus; + + dev = qdev_create(NULL, TYPE_PXA2XX_I2C); + qdev_prop_set_uint32(dev, "size", region_size + 1); + qdev_prop_set_uint32(dev, "offset", base & region_size); + qdev_init_nofail(dev); + + i2c_dev = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(i2c_dev, 0, base & ~region_size); + sysbus_connect_irq(i2c_dev, 0, irq); + + s = PXA2XX_I2C(i2c_dev); + /* FIXME: Should the slave device really be on a separate bus? */ + i2cbus = i2c_init_bus(dev, "dummy"); + dev = i2c_create_slave(i2cbus, TYPE_PXA2XX_I2C_SLAVE, 0); + s->slave = PXA2XX_I2C_SLAVE(dev); + s->slave->host = s; + + return s; +} + +static int pxa2xx_i2c_initfn(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + PXA2xxI2CState *s = PXA2XX_I2C(dev); + + s->bus = i2c_init_bus(dev, "i2c"); + + memory_region_init_io(&s->iomem, OBJECT(s), &pxa2xx_i2c_ops, s, + "pxa2xx-i2c", s->region_size); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); + + return 0; +} + +I2CBus *pxa2xx_i2c_bus(PXA2xxI2CState *s) +{ + return s->bus; +} + +static Property pxa2xx_i2c_properties[] = { + DEFINE_PROP_UINT32("size", PXA2xxI2CState, region_size, 0x10000), + DEFINE_PROP_UINT32("offset", PXA2xxI2CState, offset, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void pxa2xx_i2c_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = pxa2xx_i2c_initfn; + dc->desc = "PXA2xx I2C Bus Controller"; + dc->vmsd = &vmstate_pxa2xx_i2c; + dc->props = pxa2xx_i2c_properties; +} + +static const TypeInfo pxa2xx_i2c_info = { + .name = TYPE_PXA2XX_I2C, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PXA2xxI2CState), + .class_init = pxa2xx_i2c_class_init, +}; + +/* PXA Inter-IC Sound Controller */ +static void pxa2xx_i2s_reset(PXA2xxI2SState *i2s) +{ + i2s->rx_len = 0; + i2s->tx_len = 0; + i2s->fifo_len = 0; + i2s->clk = 0x1a; + i2s->control[0] = 0x00; + i2s->control[1] = 0x00; + i2s->status = 0x00; + i2s->mask = 0x00; +} + +#define SACR_TFTH(val) ((val >> 8) & 0xf) +#define SACR_RFTH(val) ((val >> 12) & 0xf) +#define SACR_DREC(val) (val & (1 << 3)) +#define SACR_DPRL(val) (val & (1 << 4)) + +static inline void pxa2xx_i2s_update(PXA2xxI2SState *i2s) +{ + int rfs, tfs; + rfs = SACR_RFTH(i2s->control[0]) < i2s->rx_len && + !SACR_DREC(i2s->control[1]); + tfs = (i2s->tx_len || i2s->fifo_len < SACR_TFTH(i2s->control[0])) && + i2s->enable && !SACR_DPRL(i2s->control[1]); + + qemu_set_irq(i2s->rx_dma, rfs); + qemu_set_irq(i2s->tx_dma, tfs); + + i2s->status &= 0xe0; + if (i2s->fifo_len < 16 || !i2s->enable) + i2s->status |= 1 << 0; /* TNF */ + if (i2s->rx_len) + i2s->status |= 1 << 1; /* RNE */ + if (i2s->enable) + i2s->status |= 1 << 2; /* BSY */ + if (tfs) + i2s->status |= 1 << 3; /* TFS */ + if (rfs) + i2s->status |= 1 << 4; /* RFS */ + if (!(i2s->tx_len && i2s->enable)) + i2s->status |= i2s->fifo_len << 8; /* TFL */ + i2s->status |= MAX(i2s->rx_len, 0xf) << 12; /* RFL */ + + qemu_set_irq(i2s->irq, i2s->status & i2s->mask); +} + +#define SACR0 0x00 /* Serial Audio Global Control register */ +#define SACR1 0x04 /* Serial Audio I2S/MSB-Justified Control register */ +#define SASR0 0x0c /* Serial Audio Interface and FIFO Status register */ +#define SAIMR 0x14 /* Serial Audio Interrupt Mask register */ +#define SAICR 0x18 /* Serial Audio Interrupt Clear register */ +#define SADIV 0x60 /* Serial Audio Clock Divider register */ +#define SADR 0x80 /* Serial Audio Data register */ + +static uint64_t pxa2xx_i2s_read(void *opaque, hwaddr addr, + unsigned size) +{ + PXA2xxI2SState *s = (PXA2xxI2SState *) opaque; + + switch (addr) { + case SACR0: + return s->control[0]; + case SACR1: + return s->control[1]; + case SASR0: + return s->status; + case SAIMR: + return s->mask; + case SAICR: + return 0; + case SADIV: + return s->clk; + case SADR: + if (s->rx_len > 0) { + s->rx_len --; + pxa2xx_i2s_update(s); + return s->codec_in(s->opaque); + } + return 0; + default: + printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr); + break; + } + return 0; +} + +static void pxa2xx_i2s_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + PXA2xxI2SState *s = (PXA2xxI2SState *) opaque; + uint32_t *sample; + + switch (addr) { + case SACR0: + if (value & (1 << 3)) /* RST */ + pxa2xx_i2s_reset(s); + s->control[0] = value & 0xff3d; + if (!s->enable && (value & 1) && s->tx_len) { /* ENB */ + for (sample = s->fifo; s->fifo_len > 0; s->fifo_len --, sample ++) + s->codec_out(s->opaque, *sample); + s->status &= ~(1 << 7); /* I2SOFF */ + } + if (value & (1 << 4)) /* EFWR */ + printf("%s: Attempt to use special function\n", __FUNCTION__); + s->enable = (value & 9) == 1; /* ENB && !RST*/ + pxa2xx_i2s_update(s); + break; + case SACR1: + s->control[1] = value & 0x0039; + if (value & (1 << 5)) /* ENLBF */ + printf("%s: Attempt to use loopback function\n", __FUNCTION__); + if (value & (1 << 4)) /* DPRL */ + s->fifo_len = 0; + pxa2xx_i2s_update(s); + break; + case SAIMR: + s->mask = value & 0x0078; + pxa2xx_i2s_update(s); + break; + case SAICR: + s->status &= ~(value & (3 << 5)); + pxa2xx_i2s_update(s); + break; + case SADIV: + s->clk = value & 0x007f; + break; + case SADR: + if (s->tx_len && s->enable) { + s->tx_len --; + pxa2xx_i2s_update(s); + s->codec_out(s->opaque, value); + } else if (s->fifo_len < 16) { + s->fifo[s->fifo_len ++] = value; + pxa2xx_i2s_update(s); + } + break; + default: + printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr); + } +} + +static const MemoryRegionOps pxa2xx_i2s_ops = { + .read = pxa2xx_i2s_read, + .write = pxa2xx_i2s_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription vmstate_pxa2xx_i2s = { + .name = "pxa2xx_i2s", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(control, PXA2xxI2SState, 2), + VMSTATE_UINT32(status, PXA2xxI2SState), + VMSTATE_UINT32(mask, PXA2xxI2SState), + VMSTATE_UINT32(clk, PXA2xxI2SState), + VMSTATE_INT32(enable, PXA2xxI2SState), + VMSTATE_INT32(rx_len, PXA2xxI2SState), + VMSTATE_INT32(tx_len, PXA2xxI2SState), + VMSTATE_INT32(fifo_len, PXA2xxI2SState), + VMSTATE_END_OF_LIST() + } +}; + +static void pxa2xx_i2s_data_req(void *opaque, int tx, int rx) +{ + PXA2xxI2SState *s = (PXA2xxI2SState *) opaque; + uint32_t *sample; + + /* Signal FIFO errors */ + if (s->enable && s->tx_len) + s->status |= 1 << 5; /* TUR */ + if (s->enable && s->rx_len) + s->status |= 1 << 6; /* ROR */ + + /* Should be tx - MIN(tx, s->fifo_len) but we don't really need to + * handle the cases where it makes a difference. */ + s->tx_len = tx - s->fifo_len; + s->rx_len = rx; + /* Note that is s->codec_out wasn't set, we wouldn't get called. */ + if (s->enable) + for (sample = s->fifo; s->fifo_len; s->fifo_len --, sample ++) + s->codec_out(s->opaque, *sample); + pxa2xx_i2s_update(s); +} + +static PXA2xxI2SState *pxa2xx_i2s_init(MemoryRegion *sysmem, + hwaddr base, + qemu_irq irq, qemu_irq rx_dma, qemu_irq tx_dma) +{ + PXA2xxI2SState *s = (PXA2xxI2SState *) + g_malloc0(sizeof(PXA2xxI2SState)); + + s->irq = irq; + s->rx_dma = rx_dma; + s->tx_dma = tx_dma; + s->data_req = pxa2xx_i2s_data_req; + + pxa2xx_i2s_reset(s); + + memory_region_init_io(&s->iomem, NULL, &pxa2xx_i2s_ops, s, + "pxa2xx-i2s", 0x100000); + memory_region_add_subregion(sysmem, base, &s->iomem); + + vmstate_register(NULL, base, &vmstate_pxa2xx_i2s, s); + + return s; +} + +/* PXA Fast Infra-red Communications Port */ +#define TYPE_PXA2XX_FIR "pxa2xx-fir" +#define PXA2XX_FIR(obj) OBJECT_CHECK(PXA2xxFIrState, (obj), TYPE_PXA2XX_FIR) + +struct PXA2xxFIrState { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion iomem; + qemu_irq irq; + qemu_irq rx_dma; + qemu_irq tx_dma; + uint32_t enable; + CharDriverState *chr; + + uint8_t control[3]; + uint8_t status[2]; + + uint32_t rx_len; + uint32_t rx_start; + uint8_t rx_fifo[64]; +}; + +static void pxa2xx_fir_reset(DeviceState *d) +{ + PXA2xxFIrState *s = PXA2XX_FIR(d); + + s->control[0] = 0x00; + s->control[1] = 0x00; + s->control[2] = 0x00; + s->status[0] = 0x00; + s->status[1] = 0x00; + s->enable = 0; +} + +static inline void pxa2xx_fir_update(PXA2xxFIrState *s) +{ + static const int tresh[4] = { 8, 16, 32, 0 }; + int intr = 0; + if ((s->control[0] & (1 << 4)) && /* RXE */ + s->rx_len >= tresh[s->control[2] & 3]) /* TRIG */ + s->status[0] |= 1 << 4; /* RFS */ + else + s->status[0] &= ~(1 << 4); /* RFS */ + if (s->control[0] & (1 << 3)) /* TXE */ + s->status[0] |= 1 << 3; /* TFS */ + else + s->status[0] &= ~(1 << 3); /* TFS */ + if (s->rx_len) + s->status[1] |= 1 << 2; /* RNE */ + else + s->status[1] &= ~(1 << 2); /* RNE */ + if (s->control[0] & (1 << 4)) /* RXE */ + s->status[1] |= 1 << 0; /* RSY */ + else + s->status[1] &= ~(1 << 0); /* RSY */ + + intr |= (s->control[0] & (1 << 5)) && /* RIE */ + (s->status[0] & (1 << 4)); /* RFS */ + intr |= (s->control[0] & (1 << 6)) && /* TIE */ + (s->status[0] & (1 << 3)); /* TFS */ + intr |= (s->control[2] & (1 << 4)) && /* TRAIL */ + (s->status[0] & (1 << 6)); /* EOC */ + intr |= (s->control[0] & (1 << 2)) && /* TUS */ + (s->status[0] & (1 << 1)); /* TUR */ + intr |= s->status[0] & 0x25; /* FRE, RAB, EIF */ + + qemu_set_irq(s->rx_dma, (s->status[0] >> 4) & 1); + qemu_set_irq(s->tx_dma, (s->status[0] >> 3) & 1); + + qemu_set_irq(s->irq, intr && s->enable); +} + +#define ICCR0 0x00 /* FICP Control register 0 */ +#define ICCR1 0x04 /* FICP Control register 1 */ +#define ICCR2 0x08 /* FICP Control register 2 */ +#define ICDR 0x0c /* FICP Data register */ +#define ICSR0 0x14 /* FICP Status register 0 */ +#define ICSR1 0x18 /* FICP Status register 1 */ +#define ICFOR 0x1c /* FICP FIFO Occupancy Status register */ + +static uint64_t pxa2xx_fir_read(void *opaque, hwaddr addr, + unsigned size) +{ + PXA2xxFIrState *s = (PXA2xxFIrState *) opaque; + uint8_t ret; + + switch (addr) { + case ICCR0: + return s->control[0]; + case ICCR1: + return s->control[1]; + case ICCR2: + return s->control[2]; + case ICDR: + s->status[0] &= ~0x01; + s->status[1] &= ~0x72; + if (s->rx_len) { + s->rx_len --; + ret = s->rx_fifo[s->rx_start ++]; + s->rx_start &= 63; + pxa2xx_fir_update(s); + return ret; + } + printf("%s: Rx FIFO underrun.\n", __FUNCTION__); + break; + case ICSR0: + return s->status[0]; + case ICSR1: + return s->status[1] | (1 << 3); /* TNF */ + case ICFOR: + return s->rx_len; + default: + printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr); + break; + } + return 0; +} + +static void pxa2xx_fir_write(void *opaque, hwaddr addr, + uint64_t value64, unsigned size) +{ + PXA2xxFIrState *s = (PXA2xxFIrState *) opaque; + uint32_t value = value64; + uint8_t ch; + + switch (addr) { + case ICCR0: + s->control[0] = value; + if (!(value & (1 << 4))) /* RXE */ + s->rx_len = s->rx_start = 0; + if (!(value & (1 << 3))) { /* TXE */ + /* Nop */ + } + s->enable = value & 1; /* ITR */ + if (!s->enable) + s->status[0] = 0; + pxa2xx_fir_update(s); + break; + case ICCR1: + s->control[1] = value; + break; + case ICCR2: + s->control[2] = value & 0x3f; + pxa2xx_fir_update(s); + break; + case ICDR: + if (s->control[2] & (1 << 2)) /* TXP */ + ch = value; + else + ch = ~value; + if (s->chr && s->enable && (s->control[0] & (1 << 3))) /* TXE */ + qemu_chr_fe_write(s->chr, &ch, 1); + break; + case ICSR0: + s->status[0] &= ~(value & 0x66); + pxa2xx_fir_update(s); + break; + case ICFOR: + break; + default: + printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr); + } +} + +static const MemoryRegionOps pxa2xx_fir_ops = { + .read = pxa2xx_fir_read, + .write = pxa2xx_fir_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int pxa2xx_fir_is_empty(void *opaque) +{ + PXA2xxFIrState *s = (PXA2xxFIrState *) opaque; + return (s->rx_len < 64); +} + +static void pxa2xx_fir_rx(void *opaque, const uint8_t *buf, int size) +{ + PXA2xxFIrState *s = (PXA2xxFIrState *) opaque; + if (!(s->control[0] & (1 << 4))) /* RXE */ + return; + + while (size --) { + s->status[1] |= 1 << 4; /* EOF */ + if (s->rx_len >= 64) { + s->status[1] |= 1 << 6; /* ROR */ + break; + } + + if (s->control[2] & (1 << 3)) /* RXP */ + s->rx_fifo[(s->rx_start + s->rx_len ++) & 63] = *(buf ++); + else + s->rx_fifo[(s->rx_start + s->rx_len ++) & 63] = ~*(buf ++); + } + + pxa2xx_fir_update(s); +} + +static void pxa2xx_fir_event(void *opaque, int event) +{ +} + +static void pxa2xx_fir_instance_init(Object *obj) +{ + PXA2xxFIrState *s = PXA2XX_FIR(obj); + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + + memory_region_init_io(&s->iomem, NULL, &pxa2xx_fir_ops, s, + "pxa2xx-fir", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); + sysbus_init_irq(sbd, &s->rx_dma); + sysbus_init_irq(sbd, &s->tx_dma); +} + +static void pxa2xx_fir_realize(DeviceState *dev, Error **errp) +{ + PXA2xxFIrState *s = PXA2XX_FIR(dev); + + if (s->chr) { + qemu_chr_fe_claim_no_fail(s->chr); + qemu_chr_add_handlers(s->chr, pxa2xx_fir_is_empty, + pxa2xx_fir_rx, pxa2xx_fir_event, s); + } +} + +static bool pxa2xx_fir_vmstate_validate(void *opaque, int version_id) +{ + PXA2xxFIrState *s = opaque; + + return s->rx_start < ARRAY_SIZE(s->rx_fifo); +} + +static const VMStateDescription pxa2xx_fir_vmsd = { + .name = "pxa2xx-fir", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(enable, PXA2xxFIrState), + VMSTATE_UINT8_ARRAY(control, PXA2xxFIrState, 3), + VMSTATE_UINT8_ARRAY(status, PXA2xxFIrState, 2), + VMSTATE_UINT32(rx_len, PXA2xxFIrState), + VMSTATE_UINT32(rx_start, PXA2xxFIrState), + VMSTATE_VALIDATE("fifo is 64 bytes", pxa2xx_fir_vmstate_validate), + VMSTATE_UINT8_ARRAY(rx_fifo, PXA2xxFIrState, 64), + VMSTATE_END_OF_LIST() + } +}; + +static Property pxa2xx_fir_properties[] = { + DEFINE_PROP_CHR("chardev", PXA2xxFIrState, chr), + DEFINE_PROP_END_OF_LIST(), +}; + +static void pxa2xx_fir_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = pxa2xx_fir_realize; + dc->vmsd = &pxa2xx_fir_vmsd; + dc->props = pxa2xx_fir_properties; + dc->reset = pxa2xx_fir_reset; +} + +static const TypeInfo pxa2xx_fir_info = { + .name = TYPE_PXA2XX_FIR, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PXA2xxFIrState), + .class_init = pxa2xx_fir_class_init, + .instance_init = pxa2xx_fir_instance_init, +}; + +static PXA2xxFIrState *pxa2xx_fir_init(MemoryRegion *sysmem, + hwaddr base, + qemu_irq irq, qemu_irq rx_dma, + qemu_irq tx_dma, + CharDriverState *chr) +{ + DeviceState *dev; + SysBusDevice *sbd; + + dev = qdev_create(NULL, TYPE_PXA2XX_FIR); + qdev_prop_set_chr(dev, "chardev", chr); + qdev_init_nofail(dev); + sbd = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(sbd, 0, base); + sysbus_connect_irq(sbd, 0, irq); + sysbus_connect_irq(sbd, 1, rx_dma); + sysbus_connect_irq(sbd, 2, tx_dma); + return PXA2XX_FIR(dev); +} + +static void pxa2xx_reset(void *opaque, int line, int level) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + + if (level && (s->pm_regs[PCFR >> 2] & 0x10)) { /* GPR_EN */ + cpu_reset(CPU(s->cpu)); + /* TODO: reset peripherals */ + } +} + +/* Initialise a PXA270 integrated chip (ARM based core). */ +PXA2xxState *pxa270_init(MemoryRegion *address_space, + unsigned int sdram_size, const char *revision) +{ + PXA2xxState *s; + int i; + DriveInfo *dinfo; + s = (PXA2xxState *) g_malloc0(sizeof(PXA2xxState)); + + if (revision && strncmp(revision, "pxa27", 5)) { + fprintf(stderr, "Machine requires a PXA27x processor.\n"); + exit(1); + } + if (!revision) + revision = "pxa270"; + + s->cpu = cpu_arm_init(revision); + if (s->cpu == NULL) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + s->reset = qemu_allocate_irq(pxa2xx_reset, s, 0); + + /* SDRAM & Internal Memory Storage */ + memory_region_init_ram(&s->sdram, NULL, "pxa270.sdram", sdram_size, + &error_abort); + vmstate_register_ram_global(&s->sdram); + memory_region_add_subregion(address_space, PXA2XX_SDRAM_BASE, &s->sdram); + memory_region_init_ram(&s->internal, NULL, "pxa270.internal", 0x40000, + &error_abort); + vmstate_register_ram_global(&s->internal); + memory_region_add_subregion(address_space, PXA2XX_INTERNAL_BASE, + &s->internal); + + s->pic = pxa2xx_pic_init(0x40d00000, s->cpu); + + s->dma = pxa27x_dma_init(0x40000000, + qdev_get_gpio_in(s->pic, PXA2XX_PIC_DMA)); + + sysbus_create_varargs("pxa27x-timer", 0x40a00000, + qdev_get_gpio_in(s->pic, PXA2XX_PIC_OST_0 + 0), + qdev_get_gpio_in(s->pic, PXA2XX_PIC_OST_0 + 1), + qdev_get_gpio_in(s->pic, PXA2XX_PIC_OST_0 + 2), + qdev_get_gpio_in(s->pic, PXA2XX_PIC_OST_0 + 3), + qdev_get_gpio_in(s->pic, PXA27X_PIC_OST_4_11), + NULL); + + s->gpio = pxa2xx_gpio_init(0x40e00000, s->cpu, s->pic, 121); + + dinfo = drive_get(IF_SD, 0, 0); + if (!dinfo) { + fprintf(stderr, "qemu: missing SecureDigital device\n"); + exit(1); + } + s->mmc = pxa2xx_mmci_init(address_space, 0x41100000, + blk_by_legacy_dinfo(dinfo), + qdev_get_gpio_in(s->pic, PXA2XX_PIC_MMC), + qdev_get_gpio_in(s->dma, PXA2XX_RX_RQ_MMCI), + qdev_get_gpio_in(s->dma, PXA2XX_TX_RQ_MMCI)); + + for (i = 0; pxa270_serial[i].io_base; i++) { + if (serial_hds[i]) { + serial_mm_init(address_space, pxa270_serial[i].io_base, 2, + qdev_get_gpio_in(s->pic, pxa270_serial[i].irqn), + 14857000 / 16, serial_hds[i], + DEVICE_NATIVE_ENDIAN); + } else { + break; + } + } + if (serial_hds[i]) + s->fir = pxa2xx_fir_init(address_space, 0x40800000, + qdev_get_gpio_in(s->pic, PXA2XX_PIC_ICP), + qdev_get_gpio_in(s->dma, PXA2XX_RX_RQ_ICP), + qdev_get_gpio_in(s->dma, PXA2XX_TX_RQ_ICP), + serial_hds[i]); + + s->lcd = pxa2xx_lcdc_init(address_space, 0x44000000, + qdev_get_gpio_in(s->pic, PXA2XX_PIC_LCD)); + + s->cm_base = 0x41300000; + s->cm_regs[CCCR >> 2] = 0x02000210; /* 416.0 MHz */ + s->clkcfg = 0x00000009; /* Turbo mode active */ + memory_region_init_io(&s->cm_iomem, NULL, &pxa2xx_cm_ops, s, "pxa2xx-cm", 0x1000); + memory_region_add_subregion(address_space, s->cm_base, &s->cm_iomem); + vmstate_register(NULL, 0, &vmstate_pxa2xx_cm, s); + + pxa2xx_setup_cp14(s); + + s->mm_base = 0x48000000; + s->mm_regs[MDMRS >> 2] = 0x00020002; + s->mm_regs[MDREFR >> 2] = 0x03ca4000; + s->mm_regs[MECR >> 2] = 0x00000001; /* Two PC Card sockets */ + memory_region_init_io(&s->mm_iomem, NULL, &pxa2xx_mm_ops, s, "pxa2xx-mm", 0x1000); + memory_region_add_subregion(address_space, s->mm_base, &s->mm_iomem); + vmstate_register(NULL, 0, &vmstate_pxa2xx_mm, s); + + s->pm_base = 0x40f00000; + memory_region_init_io(&s->pm_iomem, NULL, &pxa2xx_pm_ops, s, "pxa2xx-pm", 0x100); + memory_region_add_subregion(address_space, s->pm_base, &s->pm_iomem); + vmstate_register(NULL, 0, &vmstate_pxa2xx_pm, s); + + for (i = 0; pxa27x_ssp[i].io_base; i ++); + s->ssp = (SSIBus **)g_malloc0(sizeof(SSIBus *) * i); + for (i = 0; pxa27x_ssp[i].io_base; i ++) { + DeviceState *dev; + dev = sysbus_create_simple(TYPE_PXA2XX_SSP, pxa27x_ssp[i].io_base, + qdev_get_gpio_in(s->pic, pxa27x_ssp[i].irqn)); + s->ssp[i] = (SSIBus *)qdev_get_child_bus(dev, "ssi"); + } + + if (usb_enabled()) { + sysbus_create_simple("sysbus-ohci", 0x4c000000, + qdev_get_gpio_in(s->pic, PXA2XX_PIC_USBH1)); + } + + s->pcmcia[0] = pxa2xx_pcmcia_init(address_space, 0x20000000); + s->pcmcia[1] = pxa2xx_pcmcia_init(address_space, 0x30000000); + + sysbus_create_simple(TYPE_PXA2XX_RTC, 0x40900000, + qdev_get_gpio_in(s->pic, PXA2XX_PIC_RTCALARM)); + + s->i2c[0] = pxa2xx_i2c_init(0x40301600, + qdev_get_gpio_in(s->pic, PXA2XX_PIC_I2C), 0xffff); + s->i2c[1] = pxa2xx_i2c_init(0x40f00100, + qdev_get_gpio_in(s->pic, PXA2XX_PIC_PWRI2C), 0xff); + + s->i2s = pxa2xx_i2s_init(address_space, 0x40400000, + qdev_get_gpio_in(s->pic, PXA2XX_PIC_I2S), + qdev_get_gpio_in(s->dma, PXA2XX_RX_RQ_I2S), + qdev_get_gpio_in(s->dma, PXA2XX_TX_RQ_I2S)); + + s->kp = pxa27x_keypad_init(address_space, 0x41500000, + qdev_get_gpio_in(s->pic, PXA2XX_PIC_KEYPAD)); + + /* GPIO1 resets the processor */ + /* The handler can be overridden by board-specific code */ + qdev_connect_gpio_out(s->gpio, 1, s->reset); + return s; +} + +/* Initialise a PXA255 integrated chip (ARM based core). */ +PXA2xxState *pxa255_init(MemoryRegion *address_space, unsigned int sdram_size) +{ + PXA2xxState *s; + int i; + DriveInfo *dinfo; + + s = (PXA2xxState *) g_malloc0(sizeof(PXA2xxState)); + + s->cpu = cpu_arm_init("pxa255"); + if (s->cpu == NULL) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + s->reset = qemu_allocate_irq(pxa2xx_reset, s, 0); + + /* SDRAM & Internal Memory Storage */ + memory_region_init_ram(&s->sdram, NULL, "pxa255.sdram", sdram_size, + &error_abort); + vmstate_register_ram_global(&s->sdram); + memory_region_add_subregion(address_space, PXA2XX_SDRAM_BASE, &s->sdram); + memory_region_init_ram(&s->internal, NULL, "pxa255.internal", + PXA2XX_INTERNAL_SIZE, &error_abort); + vmstate_register_ram_global(&s->internal); + memory_region_add_subregion(address_space, PXA2XX_INTERNAL_BASE, + &s->internal); + + s->pic = pxa2xx_pic_init(0x40d00000, s->cpu); + + s->dma = pxa255_dma_init(0x40000000, + qdev_get_gpio_in(s->pic, PXA2XX_PIC_DMA)); + + sysbus_create_varargs("pxa25x-timer", 0x40a00000, + qdev_get_gpio_in(s->pic, PXA2XX_PIC_OST_0 + 0), + qdev_get_gpio_in(s->pic, PXA2XX_PIC_OST_0 + 1), + qdev_get_gpio_in(s->pic, PXA2XX_PIC_OST_0 + 2), + qdev_get_gpio_in(s->pic, PXA2XX_PIC_OST_0 + 3), + NULL); + + s->gpio = pxa2xx_gpio_init(0x40e00000, s->cpu, s->pic, 85); + + dinfo = drive_get(IF_SD, 0, 0); + if (!dinfo) { + fprintf(stderr, "qemu: missing SecureDigital device\n"); + exit(1); + } + s->mmc = pxa2xx_mmci_init(address_space, 0x41100000, + blk_by_legacy_dinfo(dinfo), + qdev_get_gpio_in(s->pic, PXA2XX_PIC_MMC), + qdev_get_gpio_in(s->dma, PXA2XX_RX_RQ_MMCI), + qdev_get_gpio_in(s->dma, PXA2XX_TX_RQ_MMCI)); + + for (i = 0; pxa255_serial[i].io_base; i++) { + if (serial_hds[i]) { + serial_mm_init(address_space, pxa255_serial[i].io_base, 2, + qdev_get_gpio_in(s->pic, pxa255_serial[i].irqn), + 14745600 / 16, serial_hds[i], + DEVICE_NATIVE_ENDIAN); + } else { + break; + } + } + if (serial_hds[i]) + s->fir = pxa2xx_fir_init(address_space, 0x40800000, + qdev_get_gpio_in(s->pic, PXA2XX_PIC_ICP), + qdev_get_gpio_in(s->dma, PXA2XX_RX_RQ_ICP), + qdev_get_gpio_in(s->dma, PXA2XX_TX_RQ_ICP), + serial_hds[i]); + + s->lcd = pxa2xx_lcdc_init(address_space, 0x44000000, + qdev_get_gpio_in(s->pic, PXA2XX_PIC_LCD)); + + s->cm_base = 0x41300000; + s->cm_regs[CCCR >> 2] = 0x02000210; /* 416.0 MHz */ + s->clkcfg = 0x00000009; /* Turbo mode active */ + memory_region_init_io(&s->cm_iomem, NULL, &pxa2xx_cm_ops, s, "pxa2xx-cm", 0x1000); + memory_region_add_subregion(address_space, s->cm_base, &s->cm_iomem); + vmstate_register(NULL, 0, &vmstate_pxa2xx_cm, s); + + pxa2xx_setup_cp14(s); + + s->mm_base = 0x48000000; + s->mm_regs[MDMRS >> 2] = 0x00020002; + s->mm_regs[MDREFR >> 2] = 0x03ca4000; + s->mm_regs[MECR >> 2] = 0x00000001; /* Two PC Card sockets */ + memory_region_init_io(&s->mm_iomem, NULL, &pxa2xx_mm_ops, s, "pxa2xx-mm", 0x1000); + memory_region_add_subregion(address_space, s->mm_base, &s->mm_iomem); + vmstate_register(NULL, 0, &vmstate_pxa2xx_mm, s); + + s->pm_base = 0x40f00000; + memory_region_init_io(&s->pm_iomem, NULL, &pxa2xx_pm_ops, s, "pxa2xx-pm", 0x100); + memory_region_add_subregion(address_space, s->pm_base, &s->pm_iomem); + vmstate_register(NULL, 0, &vmstate_pxa2xx_pm, s); + + for (i = 0; pxa255_ssp[i].io_base; i ++); + s->ssp = (SSIBus **)g_malloc0(sizeof(SSIBus *) * i); + for (i = 0; pxa255_ssp[i].io_base; i ++) { + DeviceState *dev; + dev = sysbus_create_simple(TYPE_PXA2XX_SSP, pxa255_ssp[i].io_base, + qdev_get_gpio_in(s->pic, pxa255_ssp[i].irqn)); + s->ssp[i] = (SSIBus *)qdev_get_child_bus(dev, "ssi"); + } + + if (usb_enabled()) { + sysbus_create_simple("sysbus-ohci", 0x4c000000, + qdev_get_gpio_in(s->pic, PXA2XX_PIC_USBH1)); + } + + s->pcmcia[0] = pxa2xx_pcmcia_init(address_space, 0x20000000); + s->pcmcia[1] = pxa2xx_pcmcia_init(address_space, 0x30000000); + + sysbus_create_simple(TYPE_PXA2XX_RTC, 0x40900000, + qdev_get_gpio_in(s->pic, PXA2XX_PIC_RTCALARM)); + + s->i2c[0] = pxa2xx_i2c_init(0x40301600, + qdev_get_gpio_in(s->pic, PXA2XX_PIC_I2C), 0xffff); + s->i2c[1] = pxa2xx_i2c_init(0x40f00100, + qdev_get_gpio_in(s->pic, PXA2XX_PIC_PWRI2C), 0xff); + + s->i2s = pxa2xx_i2s_init(address_space, 0x40400000, + qdev_get_gpio_in(s->pic, PXA2XX_PIC_I2S), + qdev_get_gpio_in(s->dma, PXA2XX_RX_RQ_I2S), + qdev_get_gpio_in(s->dma, PXA2XX_TX_RQ_I2S)); + + /* GPIO1 resets the processor */ + /* The handler can be overridden by board-specific code */ + qdev_connect_gpio_out(s->gpio, 1, s->reset); + return s; +} + +static void pxa2xx_ssp_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + sdc->init = pxa2xx_ssp_init; + dc->reset = pxa2xx_ssp_reset; + dc->vmsd = &vmstate_pxa2xx_ssp; +} + +static const TypeInfo pxa2xx_ssp_info = { + .name = TYPE_PXA2XX_SSP, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PXA2xxSSPState), + .class_init = pxa2xx_ssp_class_init, +}; + +static void pxa2xx_register_types(void) +{ + type_register_static(&pxa2xx_i2c_slave_info); + type_register_static(&pxa2xx_ssp_info); + type_register_static(&pxa2xx_i2c_info); + type_register_static(&pxa2xx_rtc_sysbus_info); + type_register_static(&pxa2xx_fir_info); +} + +type_init(pxa2xx_register_types) diff --git a/qemu/hw/arm/pxa2xx_gpio.c b/qemu/hw/arm/pxa2xx_gpio.c new file mode 100644 index 000000000..c89c8045c --- /dev/null +++ b/qemu/hw/arm/pxa2xx_gpio.c @@ -0,0 +1,356 @@ +/* + * Intel XScale PXA255/270 GPIO controller emulation. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski <balrog@zabor.org> + * + * This code is licensed under the GPL. + */ + +#include "hw/hw.h" +#include "hw/sysbus.h" +#include "hw/arm/pxa.h" + +#define PXA2XX_GPIO_BANKS 4 + +#define TYPE_PXA2XX_GPIO "pxa2xx-gpio" +#define PXA2XX_GPIO(obj) \ + OBJECT_CHECK(PXA2xxGPIOInfo, (obj), TYPE_PXA2XX_GPIO) + +typedef struct PXA2xxGPIOInfo PXA2xxGPIOInfo; +struct PXA2xxGPIOInfo { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion iomem; + qemu_irq irq0, irq1, irqX; + int lines; + int ncpu; + ARMCPU *cpu; + + /* XXX: GNU C vectors are more suitable */ + uint32_t ilevel[PXA2XX_GPIO_BANKS]; + uint32_t olevel[PXA2XX_GPIO_BANKS]; + uint32_t dir[PXA2XX_GPIO_BANKS]; + uint32_t rising[PXA2XX_GPIO_BANKS]; + uint32_t falling[PXA2XX_GPIO_BANKS]; + uint32_t status[PXA2XX_GPIO_BANKS]; + uint32_t gafr[PXA2XX_GPIO_BANKS * 2]; + + uint32_t prev_level[PXA2XX_GPIO_BANKS]; + qemu_irq handler[PXA2XX_GPIO_BANKS * 32]; + qemu_irq read_notify; +}; + +static struct { + enum { + GPIO_NONE, + GPLR, + GPSR, + GPCR, + GPDR, + GRER, + GFER, + GEDR, + GAFR_L, + GAFR_U, + } reg; + int bank; +} pxa2xx_gpio_regs[0x200] = { + [0 ... 0x1ff] = { GPIO_NONE, 0 }, +#define PXA2XX_REG(reg, a0, a1, a2, a3) \ + [a0] = { reg, 0 }, [a1] = { reg, 1 }, [a2] = { reg, 2 }, [a3] = { reg, 3 }, + + PXA2XX_REG(GPLR, 0x000, 0x004, 0x008, 0x100) + PXA2XX_REG(GPSR, 0x018, 0x01c, 0x020, 0x118) + PXA2XX_REG(GPCR, 0x024, 0x028, 0x02c, 0x124) + PXA2XX_REG(GPDR, 0x00c, 0x010, 0x014, 0x10c) + PXA2XX_REG(GRER, 0x030, 0x034, 0x038, 0x130) + PXA2XX_REG(GFER, 0x03c, 0x040, 0x044, 0x13c) + PXA2XX_REG(GEDR, 0x048, 0x04c, 0x050, 0x148) + PXA2XX_REG(GAFR_L, 0x054, 0x05c, 0x064, 0x06c) + PXA2XX_REG(GAFR_U, 0x058, 0x060, 0x068, 0x070) +}; + +static void pxa2xx_gpio_irq_update(PXA2xxGPIOInfo *s) +{ + if (s->status[0] & (1 << 0)) + qemu_irq_raise(s->irq0); + else + qemu_irq_lower(s->irq0); + + if (s->status[0] & (1 << 1)) + qemu_irq_raise(s->irq1); + else + qemu_irq_lower(s->irq1); + + if ((s->status[0] & ~3) | s->status[1] | s->status[2] | s->status[3]) + qemu_irq_raise(s->irqX); + else + qemu_irq_lower(s->irqX); +} + +/* Bitmap of pins used as standby and sleep wake-up sources. */ +static const int pxa2xx_gpio_wake[PXA2XX_GPIO_BANKS] = { + 0x8003fe1b, 0x002001fc, 0xec080000, 0x0012007f, +}; + +static void pxa2xx_gpio_set(void *opaque, int line, int level) +{ + PXA2xxGPIOInfo *s = (PXA2xxGPIOInfo *) opaque; + CPUState *cpu = CPU(s->cpu); + int bank; + uint32_t mask; + + if (line >= s->lines) { + printf("%s: No GPIO pin %i\n", __FUNCTION__, line); + return; + } + + bank = line >> 5; + mask = 1U << (line & 31); + + if (level) { + s->status[bank] |= s->rising[bank] & mask & + ~s->ilevel[bank] & ~s->dir[bank]; + s->ilevel[bank] |= mask; + } else { + s->status[bank] |= s->falling[bank] & mask & + s->ilevel[bank] & ~s->dir[bank]; + s->ilevel[bank] &= ~mask; + } + + if (s->status[bank] & mask) + pxa2xx_gpio_irq_update(s); + + /* Wake-up GPIOs */ + if (cpu->halted && (mask & ~s->dir[bank] & pxa2xx_gpio_wake[bank])) { + cpu_interrupt(cpu, CPU_INTERRUPT_EXITTB); + } +} + +static void pxa2xx_gpio_handler_update(PXA2xxGPIOInfo *s) { + uint32_t level, diff; + int i, bit, line; + for (i = 0; i < PXA2XX_GPIO_BANKS; i ++) { + level = s->olevel[i] & s->dir[i]; + + for (diff = s->prev_level[i] ^ level; diff; diff ^= 1 << bit) { + bit = ctz32(diff); + line = bit + 32 * i; + qemu_set_irq(s->handler[line], (level >> bit) & 1); + } + + s->prev_level[i] = level; + } +} + +static uint64_t pxa2xx_gpio_read(void *opaque, hwaddr offset, + unsigned size) +{ + PXA2xxGPIOInfo *s = (PXA2xxGPIOInfo *) opaque; + uint32_t ret; + int bank; + if (offset >= 0x200) + return 0; + + bank = pxa2xx_gpio_regs[offset].bank; + switch (pxa2xx_gpio_regs[offset].reg) { + case GPDR: /* GPIO Pin-Direction registers */ + return s->dir[bank]; + + case GPSR: /* GPIO Pin-Output Set registers */ + qemu_log_mask(LOG_GUEST_ERROR, + "pxa2xx GPIO: read from write only register GPSR\n"); + return 0; + + case GPCR: /* GPIO Pin-Output Clear registers */ + qemu_log_mask(LOG_GUEST_ERROR, + "pxa2xx GPIO: read from write only register GPCR\n"); + return 0; + + case GRER: /* GPIO Rising-Edge Detect Enable registers */ + return s->rising[bank]; + + case GFER: /* GPIO Falling-Edge Detect Enable registers */ + return s->falling[bank]; + + case GAFR_L: /* GPIO Alternate Function registers */ + return s->gafr[bank * 2]; + + case GAFR_U: /* GPIO Alternate Function registers */ + return s->gafr[bank * 2 + 1]; + + case GPLR: /* GPIO Pin-Level registers */ + ret = (s->olevel[bank] & s->dir[bank]) | + (s->ilevel[bank] & ~s->dir[bank]); + qemu_irq_raise(s->read_notify); + return ret; + + case GEDR: /* GPIO Edge Detect Status registers */ + return s->status[bank]; + + default: + hw_error("%s: Bad offset " REG_FMT "\n", __FUNCTION__, offset); + } + + return 0; +} + +static void pxa2xx_gpio_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + PXA2xxGPIOInfo *s = (PXA2xxGPIOInfo *) opaque; + int bank; + if (offset >= 0x200) + return; + + bank = pxa2xx_gpio_regs[offset].bank; + switch (pxa2xx_gpio_regs[offset].reg) { + case GPDR: /* GPIO Pin-Direction registers */ + s->dir[bank] = value; + pxa2xx_gpio_handler_update(s); + break; + + case GPSR: /* GPIO Pin-Output Set registers */ + s->olevel[bank] |= value; + pxa2xx_gpio_handler_update(s); + break; + + case GPCR: /* GPIO Pin-Output Clear registers */ + s->olevel[bank] &= ~value; + pxa2xx_gpio_handler_update(s); + break; + + case GRER: /* GPIO Rising-Edge Detect Enable registers */ + s->rising[bank] = value; + break; + + case GFER: /* GPIO Falling-Edge Detect Enable registers */ + s->falling[bank] = value; + break; + + case GAFR_L: /* GPIO Alternate Function registers */ + s->gafr[bank * 2] = value; + break; + + case GAFR_U: /* GPIO Alternate Function registers */ + s->gafr[bank * 2 + 1] = value; + break; + + case GEDR: /* GPIO Edge Detect Status registers */ + s->status[bank] &= ~value; + pxa2xx_gpio_irq_update(s); + break; + + default: + hw_error("%s: Bad offset " REG_FMT "\n", __FUNCTION__, offset); + } +} + +static const MemoryRegionOps pxa_gpio_ops = { + .read = pxa2xx_gpio_read, + .write = pxa2xx_gpio_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +DeviceState *pxa2xx_gpio_init(hwaddr base, + ARMCPU *cpu, DeviceState *pic, int lines) +{ + CPUState *cs = CPU(cpu); + DeviceState *dev; + + dev = qdev_create(NULL, TYPE_PXA2XX_GPIO); + qdev_prop_set_int32(dev, "lines", lines); + qdev_prop_set_int32(dev, "ncpu", cs->cpu_index); + qdev_init_nofail(dev); + + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base); + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, + qdev_get_gpio_in(pic, PXA2XX_PIC_GPIO_0)); + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 1, + qdev_get_gpio_in(pic, PXA2XX_PIC_GPIO_1)); + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 2, + qdev_get_gpio_in(pic, PXA2XX_PIC_GPIO_X)); + + return dev; +} + +static int pxa2xx_gpio_initfn(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + PXA2xxGPIOInfo *s = PXA2XX_GPIO(dev); + + s->cpu = ARM_CPU(qemu_get_cpu(s->ncpu)); + + qdev_init_gpio_in(dev, pxa2xx_gpio_set, s->lines); + qdev_init_gpio_out(dev, s->handler, s->lines); + + memory_region_init_io(&s->iomem, OBJECT(s), &pxa_gpio_ops, s, "pxa2xx-gpio", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq0); + sysbus_init_irq(sbd, &s->irq1); + sysbus_init_irq(sbd, &s->irqX); + + return 0; +} + +/* + * Registers a callback to notify on GPLR reads. This normally + * shouldn't be needed but it is used for the hack on Spitz machines. + */ +void pxa2xx_gpio_read_notifier(DeviceState *dev, qemu_irq handler) +{ + PXA2xxGPIOInfo *s = PXA2XX_GPIO(dev); + + s->read_notify = handler; +} + +static const VMStateDescription vmstate_pxa2xx_gpio_regs = { + .name = "pxa2xx-gpio", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(ilevel, PXA2xxGPIOInfo, PXA2XX_GPIO_BANKS), + VMSTATE_UINT32_ARRAY(olevel, PXA2xxGPIOInfo, PXA2XX_GPIO_BANKS), + VMSTATE_UINT32_ARRAY(dir, PXA2xxGPIOInfo, PXA2XX_GPIO_BANKS), + VMSTATE_UINT32_ARRAY(rising, PXA2xxGPIOInfo, PXA2XX_GPIO_BANKS), + VMSTATE_UINT32_ARRAY(falling, PXA2xxGPIOInfo, PXA2XX_GPIO_BANKS), + VMSTATE_UINT32_ARRAY(status, PXA2xxGPIOInfo, PXA2XX_GPIO_BANKS), + VMSTATE_UINT32_ARRAY(gafr, PXA2xxGPIOInfo, PXA2XX_GPIO_BANKS * 2), + VMSTATE_UINT32_ARRAY(prev_level, PXA2xxGPIOInfo, PXA2XX_GPIO_BANKS), + VMSTATE_END_OF_LIST(), + }, +}; + +static Property pxa2xx_gpio_properties[] = { + DEFINE_PROP_INT32("lines", PXA2xxGPIOInfo, lines, 0), + DEFINE_PROP_INT32("ncpu", PXA2xxGPIOInfo, ncpu, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void pxa2xx_gpio_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = pxa2xx_gpio_initfn; + dc->desc = "PXA2xx GPIO controller"; + dc->props = pxa2xx_gpio_properties; + dc->vmsd = &vmstate_pxa2xx_gpio_regs; +} + +static const TypeInfo pxa2xx_gpio_info = { + .name = TYPE_PXA2XX_GPIO, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PXA2xxGPIOInfo), + .class_init = pxa2xx_gpio_class_init, +}; + +static void pxa2xx_gpio_register_types(void) +{ + type_register_static(&pxa2xx_gpio_info); +} + +type_init(pxa2xx_gpio_register_types) diff --git a/qemu/hw/arm/pxa2xx_pic.c b/qemu/hw/arm/pxa2xx_pic.c new file mode 100644 index 000000000..d41ac9341 --- /dev/null +++ b/qemu/hw/arm/pxa2xx_pic.c @@ -0,0 +1,337 @@ +/* + * Intel XScale PXA Programmable Interrupt Controller. + * + * Copyright (c) 2006 Openedhand Ltd. + * Copyright (c) 2006 Thorsten Zitterell + * Written by Andrzej Zaborowski <balrog@zabor.org> + * + * This code is licensed under the GPL. + */ + +#include "hw/hw.h" +#include "hw/arm/pxa.h" +#include "hw/sysbus.h" + +#define ICIP 0x00 /* Interrupt Controller IRQ Pending register */ +#define ICMR 0x04 /* Interrupt Controller Mask register */ +#define ICLR 0x08 /* Interrupt Controller Level register */ +#define ICFP 0x0c /* Interrupt Controller FIQ Pending register */ +#define ICPR 0x10 /* Interrupt Controller Pending register */ +#define ICCR 0x14 /* Interrupt Controller Control register */ +#define ICHP 0x18 /* Interrupt Controller Highest Priority register */ +#define IPR0 0x1c /* Interrupt Controller Priority register 0 */ +#define IPR31 0x98 /* Interrupt Controller Priority register 31 */ +#define ICIP2 0x9c /* Interrupt Controller IRQ Pending register 2 */ +#define ICMR2 0xa0 /* Interrupt Controller Mask register 2 */ +#define ICLR2 0xa4 /* Interrupt Controller Level register 2 */ +#define ICFP2 0xa8 /* Interrupt Controller FIQ Pending register 2 */ +#define ICPR2 0xac /* Interrupt Controller Pending register 2 */ +#define IPR32 0xb0 /* Interrupt Controller Priority register 32 */ +#define IPR39 0xcc /* Interrupt Controller Priority register 39 */ + +#define PXA2XX_PIC_SRCS 40 + +#define TYPE_PXA2XX_PIC "pxa2xx_pic" +#define PXA2XX_PIC(obj) \ + OBJECT_CHECK(PXA2xxPICState, (obj), TYPE_PXA2XX_PIC) + +typedef struct { + /*< private >*/ + SysBusDevice parent_obj; + /*< public >*/ + + MemoryRegion iomem; + ARMCPU *cpu; + uint32_t int_enabled[2]; + uint32_t int_pending[2]; + uint32_t is_fiq[2]; + uint32_t int_idle; + uint32_t priority[PXA2XX_PIC_SRCS]; +} PXA2xxPICState; + +static void pxa2xx_pic_update(void *opaque) +{ + uint32_t mask[2]; + PXA2xxPICState *s = (PXA2xxPICState *) opaque; + CPUState *cpu = CPU(s->cpu); + + if (cpu->halted) { + mask[0] = s->int_pending[0] & (s->int_enabled[0] | s->int_idle); + mask[1] = s->int_pending[1] & (s->int_enabled[1] | s->int_idle); + if (mask[0] || mask[1]) { + cpu_interrupt(cpu, CPU_INTERRUPT_EXITTB); + } + } + + mask[0] = s->int_pending[0] & s->int_enabled[0]; + mask[1] = s->int_pending[1] & s->int_enabled[1]; + + if ((mask[0] & s->is_fiq[0]) || (mask[1] & s->is_fiq[1])) { + cpu_interrupt(cpu, CPU_INTERRUPT_FIQ); + } else { + cpu_reset_interrupt(cpu, CPU_INTERRUPT_FIQ); + } + + if ((mask[0] & ~s->is_fiq[0]) || (mask[1] & ~s->is_fiq[1])) { + cpu_interrupt(cpu, CPU_INTERRUPT_HARD); + } else { + cpu_reset_interrupt(cpu, CPU_INTERRUPT_HARD); + } +} + +/* Note: Here level means state of the signal on a pin, not + * IRQ/FIQ distinction as in PXA Developer Manual. */ +static void pxa2xx_pic_set_irq(void *opaque, int irq, int level) +{ + PXA2xxPICState *s = (PXA2xxPICState *) opaque; + int int_set = (irq >= 32); + irq &= 31; + + if (level) + s->int_pending[int_set] |= 1 << irq; + else + s->int_pending[int_set] &= ~(1 << irq); + + pxa2xx_pic_update(opaque); +} + +static inline uint32_t pxa2xx_pic_highest(PXA2xxPICState *s) { + int i, int_set, irq; + uint32_t bit, mask[2]; + uint32_t ichp = 0x003f003f; /* Both IDs invalid */ + + mask[0] = s->int_pending[0] & s->int_enabled[0]; + mask[1] = s->int_pending[1] & s->int_enabled[1]; + + for (i = PXA2XX_PIC_SRCS - 1; i >= 0; i --) { + irq = s->priority[i] & 0x3f; + if ((s->priority[i] & (1U << 31)) && irq < PXA2XX_PIC_SRCS) { + /* Source peripheral ID is valid. */ + bit = 1 << (irq & 31); + int_set = (irq >= 32); + + if (mask[int_set] & bit & s->is_fiq[int_set]) { + /* FIQ asserted */ + ichp &= 0xffff0000; + ichp |= (1 << 15) | irq; + } + + if (mask[int_set] & bit & ~s->is_fiq[int_set]) { + /* IRQ asserted */ + ichp &= 0x0000ffff; + ichp |= (1U << 31) | (irq << 16); + } + } + } + + return ichp; +} + +static uint64_t pxa2xx_pic_mem_read(void *opaque, hwaddr offset, + unsigned size) +{ + PXA2xxPICState *s = (PXA2xxPICState *) opaque; + + switch (offset) { + case ICIP: /* IRQ Pending register */ + return s->int_pending[0] & ~s->is_fiq[0] & s->int_enabled[0]; + case ICIP2: /* IRQ Pending register 2 */ + return s->int_pending[1] & ~s->is_fiq[1] & s->int_enabled[1]; + case ICMR: /* Mask register */ + return s->int_enabled[0]; + case ICMR2: /* Mask register 2 */ + return s->int_enabled[1]; + case ICLR: /* Level register */ + return s->is_fiq[0]; + case ICLR2: /* Level register 2 */ + return s->is_fiq[1]; + case ICCR: /* Idle mask */ + return (s->int_idle == 0); + case ICFP: /* FIQ Pending register */ + return s->int_pending[0] & s->is_fiq[0] & s->int_enabled[0]; + case ICFP2: /* FIQ Pending register 2 */ + return s->int_pending[1] & s->is_fiq[1] & s->int_enabled[1]; + case ICPR: /* Pending register */ + return s->int_pending[0]; + case ICPR2: /* Pending register 2 */ + return s->int_pending[1]; + case IPR0 ... IPR31: + return s->priority[0 + ((offset - IPR0 ) >> 2)]; + case IPR32 ... IPR39: + return s->priority[32 + ((offset - IPR32) >> 2)]; + case ICHP: /* Highest Priority register */ + return pxa2xx_pic_highest(s); + default: + printf("%s: Bad register offset " REG_FMT "\n", __FUNCTION__, offset); + return 0; + } +} + +static void pxa2xx_pic_mem_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + PXA2xxPICState *s = (PXA2xxPICState *) opaque; + + switch (offset) { + case ICMR: /* Mask register */ + s->int_enabled[0] = value; + break; + case ICMR2: /* Mask register 2 */ + s->int_enabled[1] = value; + break; + case ICLR: /* Level register */ + s->is_fiq[0] = value; + break; + case ICLR2: /* Level register 2 */ + s->is_fiq[1] = value; + break; + case ICCR: /* Idle mask */ + s->int_idle = (value & 1) ? 0 : ~0; + break; + case IPR0 ... IPR31: + s->priority[0 + ((offset - IPR0 ) >> 2)] = value & 0x8000003f; + break; + case IPR32 ... IPR39: + s->priority[32 + ((offset - IPR32) >> 2)] = value & 0x8000003f; + break; + default: + printf("%s: Bad register offset " REG_FMT "\n", __FUNCTION__, offset); + return; + } + pxa2xx_pic_update(opaque); +} + +/* Interrupt Controller Coprocessor Space Register Mapping */ +static const int pxa2xx_cp_reg_map[0x10] = { + [0x0 ... 0xf] = -1, + [0x0] = ICIP, + [0x1] = ICMR, + [0x2] = ICLR, + [0x3] = ICFP, + [0x4] = ICPR, + [0x5] = ICHP, + [0x6] = ICIP2, + [0x7] = ICMR2, + [0x8] = ICLR2, + [0x9] = ICFP2, + [0xa] = ICPR2, +}; + +static uint64_t pxa2xx_pic_cp_read(CPUARMState *env, const ARMCPRegInfo *ri) +{ + int offset = pxa2xx_cp_reg_map[ri->crn]; + return pxa2xx_pic_mem_read(ri->opaque, offset, 4); +} + +static void pxa2xx_pic_cp_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + int offset = pxa2xx_cp_reg_map[ri->crn]; + pxa2xx_pic_mem_write(ri->opaque, offset, value, 4); +} + +#define REGINFO_FOR_PIC_CP(NAME, CRN) \ + { .name = NAME, .cp = 6, .crn = CRN, .crm = 0, .opc1 = 0, .opc2 = 0, \ + .access = PL1_RW, .type = ARM_CP_IO, \ + .readfn = pxa2xx_pic_cp_read, .writefn = pxa2xx_pic_cp_write } + +static const ARMCPRegInfo pxa_pic_cp_reginfo[] = { + REGINFO_FOR_PIC_CP("ICIP", 0), + REGINFO_FOR_PIC_CP("ICMR", 1), + REGINFO_FOR_PIC_CP("ICLR", 2), + REGINFO_FOR_PIC_CP("ICFP", 3), + REGINFO_FOR_PIC_CP("ICPR", 4), + REGINFO_FOR_PIC_CP("ICHP", 5), + REGINFO_FOR_PIC_CP("ICIP2", 6), + REGINFO_FOR_PIC_CP("ICMR2", 7), + REGINFO_FOR_PIC_CP("ICLR2", 8), + REGINFO_FOR_PIC_CP("ICFP2", 9), + REGINFO_FOR_PIC_CP("ICPR2", 0xa), + REGINFO_SENTINEL +}; + +static const MemoryRegionOps pxa2xx_pic_ops = { + .read = pxa2xx_pic_mem_read, + .write = pxa2xx_pic_mem_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int pxa2xx_pic_post_load(void *opaque, int version_id) +{ + pxa2xx_pic_update(opaque); + return 0; +} + +DeviceState *pxa2xx_pic_init(hwaddr base, ARMCPU *cpu) +{ + DeviceState *dev = qdev_create(NULL, TYPE_PXA2XX_PIC); + PXA2xxPICState *s = PXA2XX_PIC(dev); + + s->cpu = cpu; + + s->int_pending[0] = 0; + s->int_pending[1] = 0; + s->int_enabled[0] = 0; + s->int_enabled[1] = 0; + s->is_fiq[0] = 0; + s->is_fiq[1] = 0; + + qdev_init_nofail(dev); + + qdev_init_gpio_in(dev, pxa2xx_pic_set_irq, PXA2XX_PIC_SRCS); + + /* Enable IC memory-mapped registers access. */ + memory_region_init_io(&s->iomem, OBJECT(s), &pxa2xx_pic_ops, s, + "pxa2xx-pic", 0x00100000); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base); + + /* Enable IC coprocessor access. */ + define_arm_cp_regs_with_opaque(cpu, pxa_pic_cp_reginfo, s); + + return dev; +} + +static VMStateDescription vmstate_pxa2xx_pic_regs = { + .name = "pxa2xx_pic", + .version_id = 0, + .minimum_version_id = 0, + .post_load = pxa2xx_pic_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(int_enabled, PXA2xxPICState, 2), + VMSTATE_UINT32_ARRAY(int_pending, PXA2xxPICState, 2), + VMSTATE_UINT32_ARRAY(is_fiq, PXA2xxPICState, 2), + VMSTATE_UINT32(int_idle, PXA2xxPICState), + VMSTATE_UINT32_ARRAY(priority, PXA2xxPICState, PXA2XX_PIC_SRCS), + VMSTATE_END_OF_LIST(), + }, +}; + +static int pxa2xx_pic_initfn(SysBusDevice *dev) +{ + return 0; +} + +static void pxa2xx_pic_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = pxa2xx_pic_initfn; + dc->desc = "PXA2xx PIC"; + dc->vmsd = &vmstate_pxa2xx_pic_regs; +} + +static const TypeInfo pxa2xx_pic_info = { + .name = TYPE_PXA2XX_PIC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PXA2xxPICState), + .class_init = pxa2xx_pic_class_init, +}; + +static void pxa2xx_pic_register_types(void) +{ + type_register_static(&pxa2xx_pic_info); +} + +type_init(pxa2xx_pic_register_types) diff --git a/qemu/hw/arm/realview.c b/qemu/hw/arm/realview.c new file mode 100644 index 000000000..ef2788d3e --- /dev/null +++ b/qemu/hw/arm/realview.c @@ -0,0 +1,439 @@ +/* + * ARM RealView Baseboard System emulation. + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the GPL. + */ + +#include "hw/sysbus.h" +#include "hw/arm/arm.h" +#include "hw/arm/primecell.h" +#include "hw/devices.h" +#include "hw/pci/pci.h" +#include "net/net.h" +#include "sysemu/sysemu.h" +#include "hw/boards.h" +#include "hw/i2c/i2c.h" +#include "sysemu/block-backend.h" +#include "exec/address-spaces.h" +#include "qemu/error-report.h" + +#define SMP_BOOT_ADDR 0xe0000000 +#define SMP_BOOTREG_ADDR 0x10000030 + +/* Board init. */ + +static struct arm_boot_info realview_binfo = { + .smp_loader_start = SMP_BOOT_ADDR, + .smp_bootreg_addr = SMP_BOOTREG_ADDR, +}; + +/* The following two lists must be consistent. */ +enum realview_board_type { + BOARD_EB, + BOARD_EB_MPCORE, + BOARD_PB_A8, + BOARD_PBX_A9, +}; + +static const int realview_board_id[] = { + 0x33b, + 0x33b, + 0x769, + 0x76d +}; + +static void realview_init(MachineState *machine, + enum realview_board_type board_type) +{ + ARMCPU *cpu = NULL; + CPUARMState *env; + ObjectClass *cpu_oc; + MemoryRegion *sysmem = get_system_memory(); + MemoryRegion *ram_lo; + MemoryRegion *ram_hi = g_new(MemoryRegion, 1); + MemoryRegion *ram_alias = g_new(MemoryRegion, 1); + MemoryRegion *ram_hack = g_new(MemoryRegion, 1); + DeviceState *dev, *sysctl, *gpio2, *pl041; + SysBusDevice *busdev; + qemu_irq pic[64]; + qemu_irq mmc_irq[2]; + PCIBus *pci_bus = NULL; + NICInfo *nd; + I2CBus *i2c; + int n; + int done_nic = 0; + qemu_irq cpu_irq[4]; + int is_mpcore = 0; + int is_pb = 0; + uint32_t proc_id = 0; + uint32_t sys_id; + ram_addr_t low_ram_size; + ram_addr_t ram_size = machine->ram_size; + hwaddr periphbase = 0; + + switch (board_type) { + case BOARD_EB: + break; + case BOARD_EB_MPCORE: + is_mpcore = 1; + periphbase = 0x10100000; + break; + case BOARD_PB_A8: + is_pb = 1; + break; + case BOARD_PBX_A9: + is_mpcore = 1; + is_pb = 1; + periphbase = 0x1f000000; + break; + } + + cpu_oc = cpu_class_by_name(TYPE_ARM_CPU, machine->cpu_model); + if (!cpu_oc) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + + for (n = 0; n < smp_cpus; n++) { + Object *cpuobj = object_new(object_class_get_name(cpu_oc)); + Error *err = NULL; + + /* By default A9,A15 and ARM1176 CPUs have EL3 enabled. This board + * does not currently support EL3 so the CPU EL3 property is disabled + * before realization. + */ + if (object_property_find(cpuobj, "has_el3", NULL)) { + object_property_set_bool(cpuobj, false, "has_el3", &err); + if (err) { + error_report_err(err); + exit(1); + } + } + + if (is_pb && is_mpcore) { + object_property_set_int(cpuobj, periphbase, "reset-cbar", &err); + if (err) { + error_report_err(err); + exit(1); + } + } + + object_property_set_bool(cpuobj, true, "realized", &err); + if (err) { + error_report_err(err); + exit(1); + } + + cpu_irq[n] = qdev_get_gpio_in(DEVICE(cpuobj), ARM_CPU_IRQ); + } + cpu = ARM_CPU(first_cpu); + env = &cpu->env; + if (arm_feature(env, ARM_FEATURE_V7)) { + if (is_mpcore) { + proc_id = 0x0c000000; + } else { + proc_id = 0x0e000000; + } + } else if (arm_feature(env, ARM_FEATURE_V6K)) { + proc_id = 0x06000000; + } else if (arm_feature(env, ARM_FEATURE_V6)) { + proc_id = 0x04000000; + } else { + proc_id = 0x02000000; + } + + if (is_pb && ram_size > 0x20000000) { + /* Core tile RAM. */ + ram_lo = g_new(MemoryRegion, 1); + low_ram_size = ram_size - 0x20000000; + ram_size = 0x20000000; + memory_region_init_ram(ram_lo, NULL, "realview.lowmem", low_ram_size, + &error_abort); + vmstate_register_ram_global(ram_lo); + memory_region_add_subregion(sysmem, 0x20000000, ram_lo); + } + + memory_region_init_ram(ram_hi, NULL, "realview.highmem", ram_size, + &error_abort); + vmstate_register_ram_global(ram_hi); + low_ram_size = ram_size; + if (low_ram_size > 0x10000000) + low_ram_size = 0x10000000; + /* SDRAM at address zero. */ + memory_region_init_alias(ram_alias, NULL, "realview.alias", + ram_hi, 0, low_ram_size); + memory_region_add_subregion(sysmem, 0, ram_alias); + if (is_pb) { + /* And again at a high address. */ + memory_region_add_subregion(sysmem, 0x70000000, ram_hi); + } else { + ram_size = low_ram_size; + } + + sys_id = is_pb ? 0x01780500 : 0xc1400400; + sysctl = qdev_create(NULL, "realview_sysctl"); + qdev_prop_set_uint32(sysctl, "sys_id", sys_id); + qdev_prop_set_uint32(sysctl, "proc_id", proc_id); + qdev_init_nofail(sysctl); + sysbus_mmio_map(SYS_BUS_DEVICE(sysctl), 0, 0x10000000); + + if (is_mpcore) { + dev = qdev_create(NULL, is_pb ? "a9mpcore_priv": "realview_mpcore"); + qdev_prop_set_uint32(dev, "num-cpu", smp_cpus); + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(busdev, 0, periphbase); + for (n = 0; n < smp_cpus; n++) { + sysbus_connect_irq(busdev, n, cpu_irq[n]); + } + sysbus_create_varargs("l2x0", periphbase + 0x2000, NULL); + /* Both A9 and 11MPCore put the GIC CPU i/f at base + 0x100 */ + realview_binfo.gic_cpu_if_addr = periphbase + 0x100; + } else { + uint32_t gic_addr = is_pb ? 0x1e000000 : 0x10040000; + /* For now just create the nIRQ GIC, and ignore the others. */ + dev = sysbus_create_simple("realview_gic", gic_addr, cpu_irq[0]); + } + for (n = 0; n < 64; n++) { + pic[n] = qdev_get_gpio_in(dev, n); + } + + pl041 = qdev_create(NULL, "pl041"); + qdev_prop_set_uint32(pl041, "nc_fifo_depth", 512); + qdev_init_nofail(pl041); + sysbus_mmio_map(SYS_BUS_DEVICE(pl041), 0, 0x10004000); + sysbus_connect_irq(SYS_BUS_DEVICE(pl041), 0, pic[19]); + + sysbus_create_simple("pl050_keyboard", 0x10006000, pic[20]); + sysbus_create_simple("pl050_mouse", 0x10007000, pic[21]); + + sysbus_create_simple("pl011", 0x10009000, pic[12]); + sysbus_create_simple("pl011", 0x1000a000, pic[13]); + sysbus_create_simple("pl011", 0x1000b000, pic[14]); + sysbus_create_simple("pl011", 0x1000c000, pic[15]); + + /* DMA controller is optional, apparently. */ + sysbus_create_simple("pl081", 0x10030000, pic[24]); + + sysbus_create_simple("sp804", 0x10011000, pic[4]); + sysbus_create_simple("sp804", 0x10012000, pic[5]); + + sysbus_create_simple("pl061", 0x10013000, pic[6]); + sysbus_create_simple("pl061", 0x10014000, pic[7]); + gpio2 = sysbus_create_simple("pl061", 0x10015000, pic[8]); + + sysbus_create_simple("pl111", 0x10020000, pic[23]); + + dev = sysbus_create_varargs("pl181", 0x10005000, pic[17], pic[18], NULL); + /* Wire up MMC card detect and read-only signals. These have + * to go to both the PL061 GPIO and the sysctl register. + * Note that the PL181 orders these lines (readonly,inserted) + * and the PL061 has them the other way about. Also the card + * detect line is inverted. + */ + mmc_irq[0] = qemu_irq_split( + qdev_get_gpio_in(sysctl, ARM_SYSCTL_GPIO_MMC_WPROT), + qdev_get_gpio_in(gpio2, 1)); + mmc_irq[1] = qemu_irq_split( + qdev_get_gpio_in(sysctl, ARM_SYSCTL_GPIO_MMC_CARDIN), + qemu_irq_invert(qdev_get_gpio_in(gpio2, 0))); + qdev_connect_gpio_out(dev, 0, mmc_irq[0]); + qdev_connect_gpio_out(dev, 1, mmc_irq[1]); + + sysbus_create_simple("pl031", 0x10017000, pic[10]); + + if (!is_pb) { + dev = qdev_create(NULL, "realview_pci"); + busdev = SYS_BUS_DEVICE(dev); + qdev_init_nofail(dev); + sysbus_mmio_map(busdev, 0, 0x10019000); /* PCI controller registers */ + sysbus_mmio_map(busdev, 1, 0x60000000); /* PCI self-config */ + sysbus_mmio_map(busdev, 2, 0x61000000); /* PCI config */ + sysbus_mmio_map(busdev, 3, 0x62000000); /* PCI I/O */ + sysbus_mmio_map(busdev, 4, 0x63000000); /* PCI memory window 1 */ + sysbus_mmio_map(busdev, 5, 0x64000000); /* PCI memory window 2 */ + sysbus_mmio_map(busdev, 6, 0x68000000); /* PCI memory window 3 */ + sysbus_connect_irq(busdev, 0, pic[48]); + sysbus_connect_irq(busdev, 1, pic[49]); + sysbus_connect_irq(busdev, 2, pic[50]); + sysbus_connect_irq(busdev, 3, pic[51]); + pci_bus = (PCIBus *)qdev_get_child_bus(dev, "pci"); + if (usb_enabled()) { + pci_create_simple(pci_bus, -1, "pci-ohci"); + } + n = drive_get_max_bus(IF_SCSI); + while (n >= 0) { + pci_create_simple(pci_bus, -1, "lsi53c895a"); + n--; + } + } + for(n = 0; n < nb_nics; n++) { + nd = &nd_table[n]; + + if (!done_nic && (!nd->model || + strcmp(nd->model, is_pb ? "lan9118" : "smc91c111") == 0)) { + if (is_pb) { + lan9118_init(nd, 0x4e000000, pic[28]); + } else { + smc91c111_init(nd, 0x4e000000, pic[28]); + } + done_nic = 1; + } else { + if (pci_bus) { + pci_nic_init_nofail(nd, pci_bus, "rtl8139", NULL); + } + } + } + + dev = sysbus_create_simple("versatile_i2c", 0x10002000, NULL); + i2c = (I2CBus *)qdev_get_child_bus(dev, "i2c"); + i2c_create_slave(i2c, "ds1338", 0x68); + + /* Memory map for RealView Emulation Baseboard: */ + /* 0x10000000 System registers. */ + /* 0x10001000 System controller. */ + /* 0x10002000 Two-Wire Serial Bus. */ + /* 0x10003000 Reserved. */ + /* 0x10004000 AACI. */ + /* 0x10005000 MCI. */ + /* 0x10006000 KMI0. */ + /* 0x10007000 KMI1. */ + /* 0x10008000 Character LCD. (EB) */ + /* 0x10009000 UART0. */ + /* 0x1000a000 UART1. */ + /* 0x1000b000 UART2. */ + /* 0x1000c000 UART3. */ + /* 0x1000d000 SSPI. */ + /* 0x1000e000 SCI. */ + /* 0x1000f000 Reserved. */ + /* 0x10010000 Watchdog. */ + /* 0x10011000 Timer 0+1. */ + /* 0x10012000 Timer 2+3. */ + /* 0x10013000 GPIO 0. */ + /* 0x10014000 GPIO 1. */ + /* 0x10015000 GPIO 2. */ + /* 0x10002000 Two-Wire Serial Bus - DVI. (PB) */ + /* 0x10017000 RTC. */ + /* 0x10018000 DMC. */ + /* 0x10019000 PCI controller config. */ + /* 0x10020000 CLCD. */ + /* 0x10030000 DMA Controller. */ + /* 0x10040000 GIC1. (EB) */ + /* 0x10050000 GIC2. (EB) */ + /* 0x10060000 GIC3. (EB) */ + /* 0x10070000 GIC4. (EB) */ + /* 0x10080000 SMC. */ + /* 0x1e000000 GIC1. (PB) */ + /* 0x1e001000 GIC2. (PB) */ + /* 0x1e002000 GIC3. (PB) */ + /* 0x1e003000 GIC4. (PB) */ + /* 0x40000000 NOR flash. */ + /* 0x44000000 DoC flash. */ + /* 0x48000000 SRAM. */ + /* 0x4c000000 Configuration flash. */ + /* 0x4e000000 Ethernet. */ + /* 0x4f000000 USB. */ + /* 0x50000000 PISMO. */ + /* 0x54000000 PISMO. */ + /* 0x58000000 PISMO. */ + /* 0x5c000000 PISMO. */ + /* 0x60000000 PCI. */ + /* 0x60000000 PCI Self Config. */ + /* 0x61000000 PCI Config. */ + /* 0x62000000 PCI IO. */ + /* 0x63000000 PCI mem 0. */ + /* 0x64000000 PCI mem 1. */ + /* 0x68000000 PCI mem 2. */ + + /* ??? 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. */ + memory_region_init_ram(ram_hack, NULL, "realview.hack", 0x1000, + &error_abort); + vmstate_register_ram_global(ram_hack); + memory_region_add_subregion(sysmem, SMP_BOOT_ADDR, ram_hack); + + realview_binfo.ram_size = ram_size; + realview_binfo.kernel_filename = machine->kernel_filename; + realview_binfo.kernel_cmdline = machine->kernel_cmdline; + realview_binfo.initrd_filename = machine->initrd_filename; + realview_binfo.nb_cpus = smp_cpus; + realview_binfo.board_id = realview_board_id[board_type]; + realview_binfo.loader_start = (board_type == BOARD_PB_A8 ? 0x70000000 : 0); + arm_load_kernel(ARM_CPU(first_cpu), &realview_binfo); +} + +static void realview_eb_init(MachineState *machine) +{ + if (!machine->cpu_model) { + machine->cpu_model = "arm926"; + } + realview_init(machine, BOARD_EB); +} + +static void realview_eb_mpcore_init(MachineState *machine) +{ + if (!machine->cpu_model) { + machine->cpu_model = "arm11mpcore"; + } + realview_init(machine, BOARD_EB_MPCORE); +} + +static void realview_pb_a8_init(MachineState *machine) +{ + if (!machine->cpu_model) { + machine->cpu_model = "cortex-a8"; + } + realview_init(machine, BOARD_PB_A8); +} + +static void realview_pbx_a9_init(MachineState *machine) +{ + if (!machine->cpu_model) { + machine->cpu_model = "cortex-a9"; + } + realview_init(machine, BOARD_PBX_A9); +} + +static QEMUMachine realview_eb_machine = { + .name = "realview-eb", + .desc = "ARM RealView Emulation Baseboard (ARM926EJ-S)", + .init = realview_eb_init, + .block_default_type = IF_SCSI, +}; + +static QEMUMachine realview_eb_mpcore_machine = { + .name = "realview-eb-mpcore", + .desc = "ARM RealView Emulation Baseboard (ARM11MPCore)", + .init = realview_eb_mpcore_init, + .block_default_type = IF_SCSI, + .max_cpus = 4, +}; + +static QEMUMachine realview_pb_a8_machine = { + .name = "realview-pb-a8", + .desc = "ARM RealView Platform Baseboard for Cortex-A8", + .init = realview_pb_a8_init, +}; + +static QEMUMachine realview_pbx_a9_machine = { + .name = "realview-pbx-a9", + .desc = "ARM RealView Platform Baseboard Explore for Cortex-A9", + .init = realview_pbx_a9_init, + .block_default_type = IF_SCSI, + .max_cpus = 4, +}; + +static void realview_machine_init(void) +{ + qemu_register_machine(&realview_eb_machine); + qemu_register_machine(&realview_eb_mpcore_machine); + qemu_register_machine(&realview_pb_a8_machine); + qemu_register_machine(&realview_pbx_a9_machine); +} + +machine_init(realview_machine_init); diff --git a/qemu/hw/arm/spitz.c b/qemu/hw/arm/spitz.c new file mode 100644 index 000000000..5bf032a63 --- /dev/null +++ b/qemu/hw/arm/spitz.c @@ -0,0 +1,1149 @@ +/* + * PXA270-based Clamshell PDA platforms. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski <balrog@zabor.org> + * + * This code is licensed under the GNU GPL v2. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "hw/hw.h" +#include "hw/arm/pxa.h" +#include "hw/arm/arm.h" +#include "sysemu/sysemu.h" +#include "hw/pcmcia.h" +#include "hw/i2c/i2c.h" +#include "hw/ssi.h" +#include "hw/block/flash.h" +#include "qemu/timer.h" +#include "hw/devices.h" +#include "hw/arm/sharpsl.h" +#include "ui/console.h" +#include "audio/audio.h" +#include "hw/boards.h" +#include "sysemu/block-backend.h" +#include "hw/sysbus.h" +#include "exec/address-spaces.h" + +#undef REG_FMT +#define REG_FMT "0x%02lx" + +/* Spitz Flash */ +#define FLASH_BASE 0x0c000000 +#define FLASH_ECCLPLB 0x00 /* Line parity 7 - 0 bit */ +#define FLASH_ECCLPUB 0x04 /* Line parity 15 - 8 bit */ +#define FLASH_ECCCP 0x08 /* Column parity 5 - 0 bit */ +#define FLASH_ECCCNTR 0x0c /* ECC byte counter */ +#define FLASH_ECCCLRR 0x10 /* Clear ECC */ +#define FLASH_FLASHIO 0x14 /* Flash I/O */ +#define FLASH_FLASHCTL 0x18 /* Flash Control */ + +#define FLASHCTL_CE0 (1 << 0) +#define FLASHCTL_CLE (1 << 1) +#define FLASHCTL_ALE (1 << 2) +#define FLASHCTL_WP (1 << 3) +#define FLASHCTL_CE1 (1 << 4) +#define FLASHCTL_RYBY (1 << 5) +#define FLASHCTL_NCE (FLASHCTL_CE0 | FLASHCTL_CE1) + +#define TYPE_SL_NAND "sl-nand" +#define SL_NAND(obj) OBJECT_CHECK(SLNANDState, (obj), TYPE_SL_NAND) + +typedef struct { + SysBusDevice parent_obj; + + MemoryRegion iomem; + DeviceState *nand; + uint8_t ctl; + uint8_t manf_id; + uint8_t chip_id; + ECCState ecc; +} SLNANDState; + +static uint64_t sl_read(void *opaque, hwaddr addr, unsigned size) +{ + SLNANDState *s = (SLNANDState *) opaque; + int ryby; + + switch (addr) { +#define BSHR(byte, from, to) ((s->ecc.lp[byte] >> (from - to)) & (1 << to)) + case FLASH_ECCLPLB: + return BSHR(0, 4, 0) | BSHR(0, 5, 2) | BSHR(0, 6, 4) | BSHR(0, 7, 6) | + BSHR(1, 4, 1) | BSHR(1, 5, 3) | BSHR(1, 6, 5) | BSHR(1, 7, 7); + +#define BSHL(byte, from, to) ((s->ecc.lp[byte] << (to - from)) & (1 << to)) + case FLASH_ECCLPUB: + return BSHL(0, 0, 0) | BSHL(0, 1, 2) | BSHL(0, 2, 4) | BSHL(0, 3, 6) | + BSHL(1, 0, 1) | BSHL(1, 1, 3) | BSHL(1, 2, 5) | BSHL(1, 3, 7); + + case FLASH_ECCCP: + return s->ecc.cp; + + case FLASH_ECCCNTR: + return s->ecc.count & 0xff; + + case FLASH_FLASHCTL: + nand_getpins(s->nand, &ryby); + if (ryby) + return s->ctl | FLASHCTL_RYBY; + else + return s->ctl; + + case FLASH_FLASHIO: + if (size == 4) { + return ecc_digest(&s->ecc, nand_getio(s->nand)) | + (ecc_digest(&s->ecc, nand_getio(s->nand)) << 16); + } + return ecc_digest(&s->ecc, nand_getio(s->nand)); + + default: + zaurus_printf("Bad register offset " REG_FMT "\n", (unsigned long)addr); + } + return 0; +} + +static void sl_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + SLNANDState *s = (SLNANDState *) opaque; + + switch (addr) { + case FLASH_ECCCLRR: + /* Value is ignored. */ + ecc_reset(&s->ecc); + break; + + case FLASH_FLASHCTL: + s->ctl = value & 0xff & ~FLASHCTL_RYBY; + nand_setpins(s->nand, + s->ctl & FLASHCTL_CLE, + s->ctl & FLASHCTL_ALE, + s->ctl & FLASHCTL_NCE, + s->ctl & FLASHCTL_WP, + 0); + break; + + case FLASH_FLASHIO: + nand_setio(s->nand, ecc_digest(&s->ecc, value & 0xff)); + break; + + default: + zaurus_printf("Bad register offset " REG_FMT "\n", (unsigned long)addr); + } +} + +enum { + FLASH_128M, + FLASH_1024M, +}; + +static const MemoryRegionOps sl_ops = { + .read = sl_read, + .write = sl_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void sl_flash_register(PXA2xxState *cpu, int size) +{ + DeviceState *dev; + + dev = qdev_create(NULL, TYPE_SL_NAND); + + qdev_prop_set_uint8(dev, "manf_id", NAND_MFR_SAMSUNG); + if (size == FLASH_128M) + qdev_prop_set_uint8(dev, "chip_id", 0x73); + else if (size == FLASH_1024M) + qdev_prop_set_uint8(dev, "chip_id", 0xf1); + + qdev_init_nofail(dev); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, FLASH_BASE); +} + +static int sl_nand_init(SysBusDevice *dev) +{ + SLNANDState *s = SL_NAND(dev); + DriveInfo *nand; + + s->ctl = 0; + /* FIXME use a qdev drive property instead of drive_get() */ + nand = drive_get(IF_MTD, 0, 0); + s->nand = nand_init(nand ? blk_by_legacy_dinfo(nand) : NULL, + s->manf_id, s->chip_id); + + memory_region_init_io(&s->iomem, OBJECT(s), &sl_ops, s, "sl", 0x40); + sysbus_init_mmio(dev, &s->iomem); + + return 0; +} + +/* Spitz Keyboard */ + +#define SPITZ_KEY_STROBE_NUM 11 +#define SPITZ_KEY_SENSE_NUM 7 + +static const int spitz_gpio_key_sense[SPITZ_KEY_SENSE_NUM] = { + 12, 17, 91, 34, 36, 38, 39 +}; + +static const int spitz_gpio_key_strobe[SPITZ_KEY_STROBE_NUM] = { + 88, 23, 24, 25, 26, 27, 52, 103, 107, 108, 114 +}; + +/* Eighth additional row maps the special keys */ +static int spitz_keymap[SPITZ_KEY_SENSE_NUM + 1][SPITZ_KEY_STROBE_NUM] = { + { 0x1d, 0x02, 0x04, 0x06, 0x07, 0x08, 0x0a, 0x0b, 0x0e, 0x3f, 0x40 }, + { -1 , 0x03, 0x05, 0x13, 0x15, 0x09, 0x17, 0x18, 0x19, 0x41, 0x42 }, + { 0x0f, 0x10, 0x12, 0x14, 0x22, 0x16, 0x24, 0x25, -1 , -1 , -1 }, + { 0x3c, 0x11, 0x1f, 0x21, 0x2f, 0x23, 0x32, 0x26, -1 , 0x36, -1 }, + { 0x3b, 0x1e, 0x20, 0x2e, 0x30, 0x31, 0x34, -1 , 0x1c, 0x2a, -1 }, + { 0x44, 0x2c, 0x2d, 0x0c, 0x39, 0x33, -1 , 0x48, -1 , -1 , 0x38 }, + { 0x37, 0x3d, -1 , 0x45, 0x57, 0x58, 0x4b, 0x50, 0x4d, -1 , -1 }, + { 0x52, 0x43, 0x01, 0x47, 0x49, -1 , -1 , -1 , -1 , -1 , -1 }, +}; + +#define SPITZ_GPIO_AK_INT 13 /* Remote control */ +#define SPITZ_GPIO_SYNC 16 /* Sync button */ +#define SPITZ_GPIO_ON_KEY 95 /* Power button */ +#define SPITZ_GPIO_SWA 97 /* Lid */ +#define SPITZ_GPIO_SWB 96 /* Tablet mode */ + +/* The special buttons are mapped to unused keys */ +static const int spitz_gpiomap[5] = { + SPITZ_GPIO_AK_INT, SPITZ_GPIO_SYNC, SPITZ_GPIO_ON_KEY, + SPITZ_GPIO_SWA, SPITZ_GPIO_SWB, +}; + +#define TYPE_SPITZ_KEYBOARD "spitz-keyboard" +#define SPITZ_KEYBOARD(obj) \ + OBJECT_CHECK(SpitzKeyboardState, (obj), TYPE_SPITZ_KEYBOARD) + +typedef struct { + SysBusDevice parent_obj; + + qemu_irq sense[SPITZ_KEY_SENSE_NUM]; + qemu_irq gpiomap[5]; + int keymap[0x80]; + uint16_t keyrow[SPITZ_KEY_SENSE_NUM]; + uint16_t strobe_state; + uint16_t sense_state; + + uint16_t pre_map[0x100]; + uint16_t modifiers; + uint16_t imodifiers; + uint8_t fifo[16]; + int fifopos, fifolen; + QEMUTimer *kbdtimer; +} SpitzKeyboardState; + +static void spitz_keyboard_sense_update(SpitzKeyboardState *s) +{ + int i; + uint16_t strobe, sense = 0; + for (i = 0; i < SPITZ_KEY_SENSE_NUM; i ++) { + strobe = s->keyrow[i] & s->strobe_state; + if (strobe) { + sense |= 1 << i; + if (!(s->sense_state & (1 << i))) + qemu_irq_raise(s->sense[i]); + } else if (s->sense_state & (1 << i)) + qemu_irq_lower(s->sense[i]); + } + + s->sense_state = sense; +} + +static void spitz_keyboard_strobe(void *opaque, int line, int level) +{ + SpitzKeyboardState *s = (SpitzKeyboardState *) opaque; + + if (level) + s->strobe_state |= 1 << line; + else + s->strobe_state &= ~(1 << line); + spitz_keyboard_sense_update(s); +} + +static void spitz_keyboard_keydown(SpitzKeyboardState *s, int keycode) +{ + int spitz_keycode = s->keymap[keycode & 0x7f]; + if (spitz_keycode == -1) + return; + + /* Handle the additional keys */ + if ((spitz_keycode >> 4) == SPITZ_KEY_SENSE_NUM) { + qemu_set_irq(s->gpiomap[spitz_keycode & 0xf], (keycode < 0x80)); + return; + } + + if (keycode & 0x80) + s->keyrow[spitz_keycode >> 4] &= ~(1 << (spitz_keycode & 0xf)); + else + s->keyrow[spitz_keycode >> 4] |= 1 << (spitz_keycode & 0xf); + + spitz_keyboard_sense_update(s); +} + +#define SPITZ_MOD_SHIFT (1 << 7) +#define SPITZ_MOD_CTRL (1 << 8) +#define SPITZ_MOD_FN (1 << 9) + +#define QUEUE_KEY(c) s->fifo[(s->fifopos + s->fifolen ++) & 0xf] = c + +static void spitz_keyboard_handler(void *opaque, int keycode) +{ + SpitzKeyboardState *s = opaque; + uint16_t code; + int mapcode; + switch (keycode) { + case 0x2a: /* Left Shift */ + s->modifiers |= 1; + break; + case 0xaa: + s->modifiers &= ~1; + break; + case 0x36: /* Right Shift */ + s->modifiers |= 2; + break; + case 0xb6: + s->modifiers &= ~2; + break; + case 0x1d: /* Control */ + s->modifiers |= 4; + break; + case 0x9d: + s->modifiers &= ~4; + break; + case 0x38: /* Alt */ + s->modifiers |= 8; + break; + case 0xb8: + s->modifiers &= ~8; + break; + } + + code = s->pre_map[mapcode = ((s->modifiers & 3) ? + (keycode | SPITZ_MOD_SHIFT) : + (keycode & ~SPITZ_MOD_SHIFT))]; + + if (code != mapcode) { +#if 0 + if ((code & SPITZ_MOD_SHIFT) && !(s->modifiers & 1)) { + QUEUE_KEY(0x2a | (keycode & 0x80)); + } + if ((code & SPITZ_MOD_CTRL) && !(s->modifiers & 4)) { + QUEUE_KEY(0x1d | (keycode & 0x80)); + } + if ((code & SPITZ_MOD_FN) && !(s->modifiers & 8)) { + QUEUE_KEY(0x38 | (keycode & 0x80)); + } + if ((code & SPITZ_MOD_FN) && (s->modifiers & 1)) { + QUEUE_KEY(0x2a | (~keycode & 0x80)); + } + if ((code & SPITZ_MOD_FN) && (s->modifiers & 2)) { + QUEUE_KEY(0x36 | (~keycode & 0x80)); + } +#else + if (keycode & 0x80) { + if ((s->imodifiers & 1 ) && !(s->modifiers & 1)) + QUEUE_KEY(0x2a | 0x80); + if ((s->imodifiers & 4 ) && !(s->modifiers & 4)) + QUEUE_KEY(0x1d | 0x80); + if ((s->imodifiers & 8 ) && !(s->modifiers & 8)) + QUEUE_KEY(0x38 | 0x80); + if ((s->imodifiers & 0x10) && (s->modifiers & 1)) + QUEUE_KEY(0x2a); + if ((s->imodifiers & 0x20) && (s->modifiers & 2)) + QUEUE_KEY(0x36); + s->imodifiers = 0; + } else { + if ((code & SPITZ_MOD_SHIFT) && + !((s->modifiers | s->imodifiers) & 1)) { + QUEUE_KEY(0x2a); + s->imodifiers |= 1; + } + if ((code & SPITZ_MOD_CTRL) && + !((s->modifiers | s->imodifiers) & 4)) { + QUEUE_KEY(0x1d); + s->imodifiers |= 4; + } + if ((code & SPITZ_MOD_FN) && + !((s->modifiers | s->imodifiers) & 8)) { + QUEUE_KEY(0x38); + s->imodifiers |= 8; + } + if ((code & SPITZ_MOD_FN) && (s->modifiers & 1) && + !(s->imodifiers & 0x10)) { + QUEUE_KEY(0x2a | 0x80); + s->imodifiers |= 0x10; + } + if ((code & SPITZ_MOD_FN) && (s->modifiers & 2) && + !(s->imodifiers & 0x20)) { + QUEUE_KEY(0x36 | 0x80); + s->imodifiers |= 0x20; + } + } +#endif + } + + QUEUE_KEY((code & 0x7f) | (keycode & 0x80)); +} + +static void spitz_keyboard_tick(void *opaque) +{ + SpitzKeyboardState *s = (SpitzKeyboardState *) opaque; + + if (s->fifolen) { + spitz_keyboard_keydown(s, s->fifo[s->fifopos ++]); + s->fifolen --; + if (s->fifopos >= 16) + s->fifopos = 0; + } + + timer_mod(s->kbdtimer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + get_ticks_per_sec() / 32); +} + +static void spitz_keyboard_pre_map(SpitzKeyboardState *s) +{ + int i; + for (i = 0; i < 0x100; i ++) + s->pre_map[i] = i; + s->pre_map[0x02 | SPITZ_MOD_SHIFT] = 0x02 | SPITZ_MOD_SHIFT; /* exclam */ + s->pre_map[0x28 | SPITZ_MOD_SHIFT] = 0x03 | SPITZ_MOD_SHIFT; /* quotedbl */ + s->pre_map[0x04 | SPITZ_MOD_SHIFT] = 0x04 | SPITZ_MOD_SHIFT; /* # */ + s->pre_map[0x05 | SPITZ_MOD_SHIFT] = 0x05 | SPITZ_MOD_SHIFT; /* dollar */ + s->pre_map[0x06 | SPITZ_MOD_SHIFT] = 0x06 | SPITZ_MOD_SHIFT; /* percent */ + s->pre_map[0x08 | SPITZ_MOD_SHIFT] = 0x07 | SPITZ_MOD_SHIFT; /* ampersand */ + s->pre_map[0x28] = 0x08 | SPITZ_MOD_SHIFT; /* ' */ + s->pre_map[0x0a | SPITZ_MOD_SHIFT] = 0x09 | SPITZ_MOD_SHIFT; /* ( */ + s->pre_map[0x0b | SPITZ_MOD_SHIFT] = 0x0a | SPITZ_MOD_SHIFT; /* ) */ + s->pre_map[0x29 | SPITZ_MOD_SHIFT] = 0x0b | SPITZ_MOD_SHIFT; /* tilde */ + s->pre_map[0x03 | SPITZ_MOD_SHIFT] = 0x0c | SPITZ_MOD_SHIFT; /* at */ + s->pre_map[0xd3] = 0x0e | SPITZ_MOD_FN; /* Delete */ + s->pre_map[0x3a] = 0x0f | SPITZ_MOD_FN; /* Caps_Lock */ + s->pre_map[0x07 | SPITZ_MOD_SHIFT] = 0x11 | SPITZ_MOD_FN; /* ^ */ + s->pre_map[0x0d] = 0x12 | SPITZ_MOD_FN; /* equal */ + s->pre_map[0x0d | SPITZ_MOD_SHIFT] = 0x13 | SPITZ_MOD_FN; /* plus */ + s->pre_map[0x1a] = 0x14 | SPITZ_MOD_FN; /* [ */ + s->pre_map[0x1b] = 0x15 | SPITZ_MOD_FN; /* ] */ + s->pre_map[0x1a | SPITZ_MOD_SHIFT] = 0x16 | SPITZ_MOD_FN; /* { */ + s->pre_map[0x1b | SPITZ_MOD_SHIFT] = 0x17 | SPITZ_MOD_FN; /* } */ + s->pre_map[0x27] = 0x22 | SPITZ_MOD_FN; /* semicolon */ + s->pre_map[0x27 | SPITZ_MOD_SHIFT] = 0x23 | SPITZ_MOD_FN; /* colon */ + s->pre_map[0x09 | SPITZ_MOD_SHIFT] = 0x24 | SPITZ_MOD_FN; /* asterisk */ + s->pre_map[0x2b] = 0x25 | SPITZ_MOD_FN; /* backslash */ + s->pre_map[0x2b | SPITZ_MOD_SHIFT] = 0x26 | SPITZ_MOD_FN; /* bar */ + s->pre_map[0x0c | SPITZ_MOD_SHIFT] = 0x30 | SPITZ_MOD_FN; /* _ */ + s->pre_map[0x33 | SPITZ_MOD_SHIFT] = 0x33 | SPITZ_MOD_FN; /* less */ + s->pre_map[0x35] = 0x33 | SPITZ_MOD_SHIFT; /* slash */ + s->pre_map[0x34 | SPITZ_MOD_SHIFT] = 0x34 | SPITZ_MOD_FN; /* greater */ + s->pre_map[0x35 | SPITZ_MOD_SHIFT] = 0x34 | SPITZ_MOD_SHIFT; /* question */ + s->pre_map[0x49] = 0x48 | SPITZ_MOD_FN; /* Page_Up */ + s->pre_map[0x51] = 0x50 | SPITZ_MOD_FN; /* Page_Down */ + + s->modifiers = 0; + s->imodifiers = 0; + s->fifopos = 0; + s->fifolen = 0; +} + +#undef SPITZ_MOD_SHIFT +#undef SPITZ_MOD_CTRL +#undef SPITZ_MOD_FN + +static int spitz_keyboard_post_load(void *opaque, int version_id) +{ + SpitzKeyboardState *s = (SpitzKeyboardState *) opaque; + + /* Release all pressed keys */ + memset(s->keyrow, 0, sizeof(s->keyrow)); + spitz_keyboard_sense_update(s); + s->modifiers = 0; + s->imodifiers = 0; + s->fifopos = 0; + s->fifolen = 0; + + return 0; +} + +static void spitz_keyboard_register(PXA2xxState *cpu) +{ + int i; + DeviceState *dev; + SpitzKeyboardState *s; + + dev = sysbus_create_simple(TYPE_SPITZ_KEYBOARD, -1, NULL); + s = SPITZ_KEYBOARD(dev); + + for (i = 0; i < SPITZ_KEY_SENSE_NUM; i ++) + qdev_connect_gpio_out(dev, i, qdev_get_gpio_in(cpu->gpio, spitz_gpio_key_sense[i])); + + for (i = 0; i < 5; i ++) + s->gpiomap[i] = qdev_get_gpio_in(cpu->gpio, spitz_gpiomap[i]); + + if (!graphic_rotate) + s->gpiomap[4] = qemu_irq_invert(s->gpiomap[4]); + + for (i = 0; i < 5; i++) + qemu_set_irq(s->gpiomap[i], 0); + + for (i = 0; i < SPITZ_KEY_STROBE_NUM; i ++) + qdev_connect_gpio_out(cpu->gpio, spitz_gpio_key_strobe[i], + qdev_get_gpio_in(dev, i)); + + timer_mod(s->kbdtimer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)); + + qemu_add_kbd_event_handler(spitz_keyboard_handler, s); +} + +static int spitz_keyboard_init(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + SpitzKeyboardState *s = SPITZ_KEYBOARD(dev); + int i, j; + + for (i = 0; i < 0x80; i ++) + s->keymap[i] = -1; + for (i = 0; i < SPITZ_KEY_SENSE_NUM + 1; i ++) + for (j = 0; j < SPITZ_KEY_STROBE_NUM; j ++) + if (spitz_keymap[i][j] != -1) + s->keymap[spitz_keymap[i][j]] = (i << 4) | j; + + spitz_keyboard_pre_map(s); + + s->kbdtimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, spitz_keyboard_tick, s); + qdev_init_gpio_in(dev, spitz_keyboard_strobe, SPITZ_KEY_STROBE_NUM); + qdev_init_gpio_out(dev, s->sense, SPITZ_KEY_SENSE_NUM); + + return 0; +} + +/* LCD backlight controller */ + +#define LCDTG_RESCTL 0x00 +#define LCDTG_PHACTRL 0x01 +#define LCDTG_DUTYCTRL 0x02 +#define LCDTG_POWERREG0 0x03 +#define LCDTG_POWERREG1 0x04 +#define LCDTG_GPOR3 0x05 +#define LCDTG_PICTRL 0x06 +#define LCDTG_POLCTRL 0x07 + +typedef struct { + SSISlave ssidev; + uint32_t bl_intensity; + uint32_t bl_power; +} SpitzLCDTG; + +static void spitz_bl_update(SpitzLCDTG *s) +{ + if (s->bl_power && s->bl_intensity) + zaurus_printf("LCD Backlight now at %i/63\n", s->bl_intensity); + else + zaurus_printf("LCD Backlight now off\n"); +} + +/* FIXME: Implement GPIO properly and remove this hack. */ +static SpitzLCDTG *spitz_lcdtg; + +static inline void spitz_bl_bit5(void *opaque, int line, int level) +{ + SpitzLCDTG *s = spitz_lcdtg; + int prev = s->bl_intensity; + + if (level) + s->bl_intensity &= ~0x20; + else + s->bl_intensity |= 0x20; + + if (s->bl_power && prev != s->bl_intensity) + spitz_bl_update(s); +} + +static inline void spitz_bl_power(void *opaque, int line, int level) +{ + SpitzLCDTG *s = spitz_lcdtg; + s->bl_power = !!level; + spitz_bl_update(s); +} + +static uint32_t spitz_lcdtg_transfer(SSISlave *dev, uint32_t value) +{ + SpitzLCDTG *s = FROM_SSI_SLAVE(SpitzLCDTG, dev); + int addr; + addr = value >> 5; + value &= 0x1f; + + switch (addr) { + case LCDTG_RESCTL: + if (value) + zaurus_printf("LCD in QVGA mode\n"); + else + zaurus_printf("LCD in VGA mode\n"); + break; + + case LCDTG_DUTYCTRL: + s->bl_intensity &= ~0x1f; + s->bl_intensity |= value; + if (s->bl_power) + spitz_bl_update(s); + break; + + case LCDTG_POWERREG0: + /* Set common voltage to M62332FP */ + break; + } + return 0; +} + +static int spitz_lcdtg_init(SSISlave *dev) +{ + SpitzLCDTG *s = FROM_SSI_SLAVE(SpitzLCDTG, dev); + + spitz_lcdtg = s; + s->bl_power = 0; + s->bl_intensity = 0x20; + + return 0; +} + +/* SSP devices */ + +#define CORGI_SSP_PORT 2 + +#define SPITZ_GPIO_LCDCON_CS 53 +#define SPITZ_GPIO_ADS7846_CS 14 +#define SPITZ_GPIO_MAX1111_CS 20 +#define SPITZ_GPIO_TP_INT 11 + +static DeviceState *max1111; + +/* "Demux" the signal based on current chipselect */ +typedef struct { + SSISlave ssidev; + SSIBus *bus[3]; + uint32_t enable[3]; +} CorgiSSPState; + +static uint32_t corgi_ssp_transfer(SSISlave *dev, uint32_t value) +{ + CorgiSSPState *s = FROM_SSI_SLAVE(CorgiSSPState, dev); + int i; + + for (i = 0; i < 3; i++) { + if (s->enable[i]) { + return ssi_transfer(s->bus[i], value); + } + } + return 0; +} + +static void corgi_ssp_gpio_cs(void *opaque, int line, int level) +{ + CorgiSSPState *s = (CorgiSSPState *)opaque; + assert(line >= 0 && line < 3); + s->enable[line] = !level; +} + +#define MAX1111_BATT_VOLT 1 +#define MAX1111_BATT_TEMP 2 +#define MAX1111_ACIN_VOLT 3 + +#define SPITZ_BATTERY_TEMP 0xe0 /* About 2.9V */ +#define SPITZ_BATTERY_VOLT 0xd0 /* About 4.0V */ +#define SPITZ_CHARGEON_ACIN 0x80 /* About 5.0V */ + +static void spitz_adc_temp_on(void *opaque, int line, int level) +{ + if (!max1111) + return; + + if (level) + max111x_set_input(max1111, MAX1111_BATT_TEMP, SPITZ_BATTERY_TEMP); + else + max111x_set_input(max1111, MAX1111_BATT_TEMP, 0); +} + +static int corgi_ssp_init(SSISlave *d) +{ + DeviceState *dev = DEVICE(d); + CorgiSSPState *s = FROM_SSI_SLAVE(CorgiSSPState, d); + + qdev_init_gpio_in(dev, corgi_ssp_gpio_cs, 3); + s->bus[0] = ssi_create_bus(dev, "ssi0"); + s->bus[1] = ssi_create_bus(dev, "ssi1"); + s->bus[2] = ssi_create_bus(dev, "ssi2"); + + return 0; +} + +static void spitz_ssp_attach(PXA2xxState *cpu) +{ + DeviceState *mux; + DeviceState *dev; + void *bus; + + mux = ssi_create_slave(cpu->ssp[CORGI_SSP_PORT - 1], "corgi-ssp"); + + bus = qdev_get_child_bus(mux, "ssi0"); + ssi_create_slave(bus, "spitz-lcdtg"); + + bus = qdev_get_child_bus(mux, "ssi1"); + dev = ssi_create_slave(bus, "ads7846"); + qdev_connect_gpio_out(dev, 0, + qdev_get_gpio_in(cpu->gpio, SPITZ_GPIO_TP_INT)); + + bus = qdev_get_child_bus(mux, "ssi2"); + max1111 = ssi_create_slave(bus, "max1111"); + max111x_set_input(max1111, MAX1111_BATT_VOLT, SPITZ_BATTERY_VOLT); + max111x_set_input(max1111, MAX1111_BATT_TEMP, 0); + max111x_set_input(max1111, MAX1111_ACIN_VOLT, SPITZ_CHARGEON_ACIN); + + qdev_connect_gpio_out(cpu->gpio, SPITZ_GPIO_LCDCON_CS, + qdev_get_gpio_in(mux, 0)); + qdev_connect_gpio_out(cpu->gpio, SPITZ_GPIO_ADS7846_CS, + qdev_get_gpio_in(mux, 1)); + qdev_connect_gpio_out(cpu->gpio, SPITZ_GPIO_MAX1111_CS, + qdev_get_gpio_in(mux, 2)); +} + +/* CF Microdrive */ + +static void spitz_microdrive_attach(PXA2xxState *cpu, int slot) +{ + PCMCIACardState *md; + DriveInfo *dinfo; + + dinfo = drive_get(IF_IDE, 0, 0); + if (!dinfo || dinfo->media_cd) + return; + md = dscm1xxxx_init(dinfo); + pxa2xx_pcmcia_attach(cpu->pcmcia[slot], md); +} + +/* Wm8750 and Max7310 on I2C */ + +#define AKITA_MAX_ADDR 0x18 +#define SPITZ_WM_ADDRL 0x1b +#define SPITZ_WM_ADDRH 0x1a + +#define SPITZ_GPIO_WM 5 + +static void spitz_wm8750_addr(void *opaque, int line, int level) +{ + I2CSlave *wm = (I2CSlave *) opaque; + if (level) + i2c_set_slave_address(wm, SPITZ_WM_ADDRH); + else + i2c_set_slave_address(wm, SPITZ_WM_ADDRL); +} + +static void spitz_i2c_setup(PXA2xxState *cpu) +{ + /* Attach the CPU on one end of our I2C bus. */ + I2CBus *bus = pxa2xx_i2c_bus(cpu->i2c[0]); + + DeviceState *wm; + + /* Attach a WM8750 to the bus */ + wm = i2c_create_slave(bus, "wm8750", 0); + + spitz_wm8750_addr(wm, 0, 0); + qdev_connect_gpio_out(cpu->gpio, SPITZ_GPIO_WM, + qemu_allocate_irq(spitz_wm8750_addr, wm, 0)); + /* .. and to the sound interface. */ + cpu->i2s->opaque = wm; + cpu->i2s->codec_out = wm8750_dac_dat; + cpu->i2s->codec_in = wm8750_adc_dat; + wm8750_data_req_set(wm, cpu->i2s->data_req, cpu->i2s); +} + +static void spitz_akita_i2c_setup(PXA2xxState *cpu) +{ + /* Attach a Max7310 to Akita I2C bus. */ + i2c_create_slave(pxa2xx_i2c_bus(cpu->i2c[0]), "max7310", + AKITA_MAX_ADDR); +} + +/* Other peripherals */ + +static void spitz_out_switch(void *opaque, int line, int level) +{ + switch (line) { + case 0: + zaurus_printf("Charging %s.\n", level ? "off" : "on"); + break; + case 1: + zaurus_printf("Discharging %s.\n", level ? "on" : "off"); + break; + case 2: + zaurus_printf("Green LED %s.\n", level ? "on" : "off"); + break; + case 3: + zaurus_printf("Orange LED %s.\n", level ? "on" : "off"); + break; + case 4: + spitz_bl_bit5(opaque, line, level); + break; + case 5: + spitz_bl_power(opaque, line, level); + break; + case 6: + spitz_adc_temp_on(opaque, line, level); + break; + } +} + +#define SPITZ_SCP_LED_GREEN 1 +#define SPITZ_SCP_JK_B 2 +#define SPITZ_SCP_CHRG_ON 3 +#define SPITZ_SCP_MUTE_L 4 +#define SPITZ_SCP_MUTE_R 5 +#define SPITZ_SCP_CF_POWER 6 +#define SPITZ_SCP_LED_ORANGE 7 +#define SPITZ_SCP_JK_A 8 +#define SPITZ_SCP_ADC_TEMP_ON 9 +#define SPITZ_SCP2_IR_ON 1 +#define SPITZ_SCP2_AKIN_PULLUP 2 +#define SPITZ_SCP2_BACKLIGHT_CONT 7 +#define SPITZ_SCP2_BACKLIGHT_ON 8 +#define SPITZ_SCP2_MIC_BIAS 9 + +static void spitz_scoop_gpio_setup(PXA2xxState *cpu, + DeviceState *scp0, DeviceState *scp1) +{ + qemu_irq *outsignals = qemu_allocate_irqs(spitz_out_switch, cpu, 8); + + qdev_connect_gpio_out(scp0, SPITZ_SCP_CHRG_ON, outsignals[0]); + qdev_connect_gpio_out(scp0, SPITZ_SCP_JK_B, outsignals[1]); + qdev_connect_gpio_out(scp0, SPITZ_SCP_LED_GREEN, outsignals[2]); + qdev_connect_gpio_out(scp0, SPITZ_SCP_LED_ORANGE, outsignals[3]); + + if (scp1) { + qdev_connect_gpio_out(scp1, SPITZ_SCP2_BACKLIGHT_CONT, outsignals[4]); + qdev_connect_gpio_out(scp1, SPITZ_SCP2_BACKLIGHT_ON, outsignals[5]); + } + + qdev_connect_gpio_out(scp0, SPITZ_SCP_ADC_TEMP_ON, outsignals[6]); +} + +#define SPITZ_GPIO_HSYNC 22 +#define SPITZ_GPIO_SD_DETECT 9 +#define SPITZ_GPIO_SD_WP 81 +#define SPITZ_GPIO_ON_RESET 89 +#define SPITZ_GPIO_BAT_COVER 90 +#define SPITZ_GPIO_CF1_IRQ 105 +#define SPITZ_GPIO_CF1_CD 94 +#define SPITZ_GPIO_CF2_IRQ 106 +#define SPITZ_GPIO_CF2_CD 93 + +static int spitz_hsync; + +static void spitz_lcd_hsync_handler(void *opaque, int line, int level) +{ + PXA2xxState *cpu = (PXA2xxState *) opaque; + qemu_set_irq(qdev_get_gpio_in(cpu->gpio, SPITZ_GPIO_HSYNC), spitz_hsync); + spitz_hsync ^= 1; +} + +static void spitz_gpio_setup(PXA2xxState *cpu, int slots) +{ + qemu_irq lcd_hsync; + /* + * Bad hack: We toggle the LCD hsync GPIO on every GPIO status + * read to satisfy broken guests that poll-wait for hsync. + * Simulating a real hsync event would be less practical and + * wouldn't guarantee that a guest ever exits the loop. + */ + spitz_hsync = 0; + lcd_hsync = qemu_allocate_irq(spitz_lcd_hsync_handler, cpu, 0); + pxa2xx_gpio_read_notifier(cpu->gpio, lcd_hsync); + pxa2xx_lcd_vsync_notifier(cpu->lcd, lcd_hsync); + + /* MMC/SD host */ + pxa2xx_mmci_handlers(cpu->mmc, + qdev_get_gpio_in(cpu->gpio, SPITZ_GPIO_SD_WP), + qdev_get_gpio_in(cpu->gpio, SPITZ_GPIO_SD_DETECT)); + + /* Battery lock always closed */ + qemu_irq_raise(qdev_get_gpio_in(cpu->gpio, SPITZ_GPIO_BAT_COVER)); + + /* Handle reset */ + qdev_connect_gpio_out(cpu->gpio, SPITZ_GPIO_ON_RESET, cpu->reset); + + /* PCMCIA signals: card's IRQ and Card-Detect */ + if (slots >= 1) + pxa2xx_pcmcia_set_irq_cb(cpu->pcmcia[0], + qdev_get_gpio_in(cpu->gpio, SPITZ_GPIO_CF1_IRQ), + qdev_get_gpio_in(cpu->gpio, SPITZ_GPIO_CF1_CD)); + if (slots >= 2) + pxa2xx_pcmcia_set_irq_cb(cpu->pcmcia[1], + qdev_get_gpio_in(cpu->gpio, SPITZ_GPIO_CF2_IRQ), + qdev_get_gpio_in(cpu->gpio, SPITZ_GPIO_CF2_CD)); +} + +/* Board init. */ +enum spitz_model_e { spitz, akita, borzoi, terrier }; + +#define SPITZ_RAM 0x04000000 +#define SPITZ_ROM 0x00800000 + +static struct arm_boot_info spitz_binfo = { + .loader_start = PXA2XX_SDRAM_BASE, + .ram_size = 0x04000000, +}; + +static void spitz_common_init(MachineState *machine, + enum spitz_model_e model, int arm_id) +{ + PXA2xxState *mpu; + DeviceState *scp0, *scp1 = NULL; + MemoryRegion *address_space_mem = get_system_memory(); + MemoryRegion *rom = g_new(MemoryRegion, 1); + const char *cpu_model = machine->cpu_model; + + if (!cpu_model) + cpu_model = (model == terrier) ? "pxa270-c5" : "pxa270-c0"; + + /* Setup CPU & memory */ + mpu = pxa270_init(address_space_mem, spitz_binfo.ram_size, cpu_model); + + sl_flash_register(mpu, (model == spitz) ? FLASH_128M : FLASH_1024M); + + memory_region_init_ram(rom, NULL, "spitz.rom", SPITZ_ROM, &error_abort); + vmstate_register_ram_global(rom); + memory_region_set_readonly(rom, true); + memory_region_add_subregion(address_space_mem, 0, rom); + + /* Setup peripherals */ + spitz_keyboard_register(mpu); + + spitz_ssp_attach(mpu); + + scp0 = sysbus_create_simple("scoop", 0x10800000, NULL); + if (model != akita) { + scp1 = sysbus_create_simple("scoop", 0x08800040, NULL); + } + + spitz_scoop_gpio_setup(mpu, scp0, scp1); + + spitz_gpio_setup(mpu, (model == akita) ? 1 : 2); + + spitz_i2c_setup(mpu); + + if (model == akita) + spitz_akita_i2c_setup(mpu); + + if (model == terrier) + /* A 6.0 GB microdrive is permanently sitting in CF slot 1. */ + spitz_microdrive_attach(mpu, 1); + else if (model != akita) + /* A 4.0 GB microdrive is permanently sitting in CF slot 0. */ + spitz_microdrive_attach(mpu, 0); + + spitz_binfo.kernel_filename = machine->kernel_filename; + spitz_binfo.kernel_cmdline = machine->kernel_cmdline; + spitz_binfo.initrd_filename = machine->initrd_filename; + spitz_binfo.board_id = arm_id; + arm_load_kernel(mpu->cpu, &spitz_binfo); + sl_bootparam_write(SL_PXA_PARAM_BASE); +} + +static void spitz_init(MachineState *machine) +{ + spitz_common_init(machine, spitz, 0x2c9); +} + +static void borzoi_init(MachineState *machine) +{ + spitz_common_init(machine, borzoi, 0x33f); +} + +static void akita_init(MachineState *machine) +{ + spitz_common_init(machine, akita, 0x2e8); +} + +static void terrier_init(MachineState *machine) +{ + spitz_common_init(machine, terrier, 0x33f); +} + +static QEMUMachine akitapda_machine = { + .name = "akita", + .desc = "Akita PDA (PXA270)", + .init = akita_init, +}; + +static QEMUMachine spitzpda_machine = { + .name = "spitz", + .desc = "Spitz PDA (PXA270)", + .init = spitz_init, +}; + +static QEMUMachine borzoipda_machine = { + .name = "borzoi", + .desc = "Borzoi PDA (PXA270)", + .init = borzoi_init, +}; + +static QEMUMachine terrierpda_machine = { + .name = "terrier", + .desc = "Terrier PDA (PXA270)", + .init = terrier_init, +}; + +static void spitz_machine_init(void) +{ + qemu_register_machine(&akitapda_machine); + qemu_register_machine(&spitzpda_machine); + qemu_register_machine(&borzoipda_machine); + qemu_register_machine(&terrierpda_machine); +} + +machine_init(spitz_machine_init); + +static bool is_version_0(void *opaque, int version_id) +{ + return version_id == 0; +} + +static VMStateDescription vmstate_sl_nand_info = { + .name = "sl-nand", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT8(ctl, SLNANDState), + VMSTATE_STRUCT(ecc, SLNANDState, 0, vmstate_ecc_state, ECCState), + VMSTATE_END_OF_LIST(), + }, +}; + +static Property sl_nand_properties[] = { + DEFINE_PROP_UINT8("manf_id", SLNANDState, manf_id, NAND_MFR_SAMSUNG), + DEFINE_PROP_UINT8("chip_id", SLNANDState, chip_id, 0xf1), + DEFINE_PROP_END_OF_LIST(), +}; + +static void sl_nand_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = sl_nand_init; + dc->vmsd = &vmstate_sl_nand_info; + dc->props = sl_nand_properties; + /* Reason: init() method uses drive_get() */ + dc->cannot_instantiate_with_device_add_yet = true; +} + +static const TypeInfo sl_nand_info = { + .name = TYPE_SL_NAND, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SLNANDState), + .class_init = sl_nand_class_init, +}; + +static VMStateDescription vmstate_spitz_kbd = { + .name = "spitz-keyboard", + .version_id = 1, + .minimum_version_id = 0, + .post_load = spitz_keyboard_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT16(sense_state, SpitzKeyboardState), + VMSTATE_UINT16(strobe_state, SpitzKeyboardState), + VMSTATE_UNUSED_TEST(is_version_0, 5), + VMSTATE_END_OF_LIST(), + }, +}; + +static Property spitz_keyboard_properties[] = { + DEFINE_PROP_END_OF_LIST(), +}; + +static void spitz_keyboard_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = spitz_keyboard_init; + dc->vmsd = &vmstate_spitz_kbd; + dc->props = spitz_keyboard_properties; +} + +static const TypeInfo spitz_keyboard_info = { + .name = TYPE_SPITZ_KEYBOARD, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SpitzKeyboardState), + .class_init = spitz_keyboard_class_init, +}; + +static const VMStateDescription vmstate_corgi_ssp_regs = { + .name = "corgi-ssp", + .version_id = 2, + .minimum_version_id = 2, + .fields = (VMStateField[]) { + VMSTATE_SSI_SLAVE(ssidev, CorgiSSPState), + VMSTATE_UINT32_ARRAY(enable, CorgiSSPState, 3), + VMSTATE_END_OF_LIST(), + } +}; + +static void corgi_ssp_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SSISlaveClass *k = SSI_SLAVE_CLASS(klass); + + k->init = corgi_ssp_init; + k->transfer = corgi_ssp_transfer; + dc->vmsd = &vmstate_corgi_ssp_regs; +} + +static const TypeInfo corgi_ssp_info = { + .name = "corgi-ssp", + .parent = TYPE_SSI_SLAVE, + .instance_size = sizeof(CorgiSSPState), + .class_init = corgi_ssp_class_init, +}; + +static const VMStateDescription vmstate_spitz_lcdtg_regs = { + .name = "spitz-lcdtg", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_SSI_SLAVE(ssidev, SpitzLCDTG), + VMSTATE_UINT32(bl_intensity, SpitzLCDTG), + VMSTATE_UINT32(bl_power, SpitzLCDTG), + VMSTATE_END_OF_LIST(), + } +}; + +static void spitz_lcdtg_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SSISlaveClass *k = SSI_SLAVE_CLASS(klass); + + k->init = spitz_lcdtg_init; + k->transfer = spitz_lcdtg_transfer; + dc->vmsd = &vmstate_spitz_lcdtg_regs; +} + +static const TypeInfo spitz_lcdtg_info = { + .name = "spitz-lcdtg", + .parent = TYPE_SSI_SLAVE, + .instance_size = sizeof(SpitzLCDTG), + .class_init = spitz_lcdtg_class_init, +}; + +static void spitz_register_types(void) +{ + type_register_static(&corgi_ssp_info); + type_register_static(&spitz_lcdtg_info); + type_register_static(&spitz_keyboard_info); + type_register_static(&sl_nand_info); +} + +type_init(spitz_register_types) diff --git a/qemu/hw/arm/stellaris.c b/qemu/hw/arm/stellaris.c new file mode 100644 index 000000000..cb515ec76 --- /dev/null +++ b/qemu/hw/arm/stellaris.c @@ -0,0 +1,1438 @@ +/* + * Luminary Micro Stellaris peripherals + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the GPL. + */ + +#include "hw/sysbus.h" +#include "hw/ssi.h" +#include "hw/arm/arm.h" +#include "hw/devices.h" +#include "qemu/timer.h" +#include "hw/i2c/i2c.h" +#include "net/net.h" +#include "hw/boards.h" +#include "exec/address-spaces.h" + +#define GPIO_A 0 +#define GPIO_B 1 +#define GPIO_C 2 +#define GPIO_D 3 +#define GPIO_E 4 +#define GPIO_F 5 +#define GPIO_G 6 + +#define BP_OLED_I2C 0x01 +#define BP_OLED_SSI 0x02 +#define BP_GAMEPAD 0x04 + +#define NUM_IRQ_LINES 64 + +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; + uint32_t peripherals; +} stellaris_board_info; + +/* General purpose timer module. */ + +#define TYPE_STELLARIS_GPTM "stellaris-gptm" +#define STELLARIS_GPTM(obj) \ + OBJECT_CHECK(gptm_state, (obj), TYPE_STELLARIS_GPTM) + +typedef struct gptm_state { + SysBusDevice parent_obj; + + MemoryRegion iomem; + 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]; + 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) +{ + timer_del(s->timer[n]); +} + +static void gptm_reload(gptm_state *s, int n, int reset) +{ + int64_t tick; + if (reset) + tick = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + 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 * system_clock_scale; + } else if (s->config == 1) { + /* 32-bit RTC. 1Hz tick. */ + tick += get_ticks_per_sec(); + } else if (s->mode[n] == 0xa) { + /* PWM mode. Not implemented. */ + } else { + hw_error("TODO: 16-bit timer mode 0x%x\n", s->mode[n]); + } + s->tick[n] = tick; + timer_mod(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_pulse(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 { + hw_error("TODO: 16-bit timer mode 0x%x\n", s->mode[n]); + } + gptm_update_irq(s); +} + +static uint64_t gptm_read(void *opaque, hwaddr offset, + unsigned size) +{ + gptm_state *s = (gptm_state *)opaque; + + 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->config == 1) { + return s->rtc; + } + qemu_log_mask(LOG_UNIMP, + "GPTM: read of TAR but timer read not supported"); + return 0; + case 0x4c: /* TBR */ + qemu_log_mask(LOG_UNIMP, + "GPTM: read of TBR but timer read not supported"); + return 0; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "GPTM: read at bad offset 0x%x\n", (int)offset); + return 0; + } +} + +static void gptm_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + gptm_state *s = (gptm_state *)opaque; + uint32_t oldval; + + /* 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: + hw_error("gptm_write: Bad offset 0x%x\n", (int)offset); + } + gptm_update_irq(s); +} + +static const MemoryRegionOps gptm_ops = { + .read = gptm_read, + .write = gptm_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription vmstate_stellaris_gptm = { + .name = "stellaris_gptm", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(config, gptm_state), + VMSTATE_UINT32_ARRAY(mode, gptm_state, 2), + VMSTATE_UINT32(control, gptm_state), + VMSTATE_UINT32(state, gptm_state), + VMSTATE_UINT32(mask, gptm_state), + VMSTATE_UNUSED(8), + VMSTATE_UINT32_ARRAY(load, gptm_state, 2), + VMSTATE_UINT32_ARRAY(match, gptm_state, 2), + VMSTATE_UINT32_ARRAY(prescale, gptm_state, 2), + VMSTATE_UINT32_ARRAY(match_prescale, gptm_state, 2), + VMSTATE_UINT32(rtc, gptm_state), + VMSTATE_INT64_ARRAY(tick, gptm_state, 2), + VMSTATE_TIMER_PTR_ARRAY(timer, gptm_state, 2), + VMSTATE_END_OF_LIST() + } +}; + +static int stellaris_gptm_init(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + gptm_state *s = STELLARIS_GPTM(dev); + + sysbus_init_irq(sbd, &s->irq); + qdev_init_gpio_out(dev, &s->trigger, 1); + + memory_region_init_io(&s->iomem, OBJECT(s), &gptm_ops, s, + "gptm", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + + s->opaque[0] = s->opaque[1] = s; + s->timer[0] = timer_new_ns(QEMU_CLOCK_VIRTUAL, gptm_tick, &s->opaque[0]); + s->timer[1] = timer_new_ns(QEMU_CLOCK_VIRTUAL, gptm_tick, &s->opaque[1]); + vmstate_register(dev, -1, &vmstate_stellaris_gptm, s); + return 0; +} + + +/* System controller. */ + +typedef struct { + MemoryRegion iomem; + uint32_t pborctl; + uint32_t ldopctl; + uint32_t int_status; + uint32_t int_mask; + uint32_t resc; + uint32_t rcc; + uint32_t rcc2; + uint32_t rcgc[3]; + uint32_t scgc[3]; + uint32_t dcgc[3]; + uint32_t clkvclr; + uint32_t ldoarst; + uint32_t user0; + uint32_t user1; + 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 */ +}; + +#define DID0_VER_MASK 0x70000000 +#define DID0_VER_0 0x00000000 +#define DID0_VER_1 0x10000000 + +#define DID0_CLASS_MASK 0x00FF0000 +#define DID0_CLASS_SANDSTORM 0x00000000 +#define DID0_CLASS_FURY 0x00010000 + +static int ssys_board_class(const ssys_state *s) +{ + uint32_t did0 = s->board->did0; + switch (did0 & DID0_VER_MASK) { + case DID0_VER_0: + return DID0_CLASS_SANDSTORM; + case DID0_VER_1: + switch (did0 & DID0_CLASS_MASK) { + case DID0_CLASS_SANDSTORM: + case DID0_CLASS_FURY: + return did0 & DID0_CLASS_MASK; + } + /* for unknown classes, fall through */ + default: + hw_error("ssys_board_class: Unknown class 0x%08x\n", did0); + } +} + +static uint64_t ssys_read(void *opaque, hwaddr offset, + unsigned size) +{ + ssys_state *s = (ssys_state *)opaque; + + 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; + switch (ssys_board_class(s)) { + case DID0_CLASS_FURY: + return pllcfg_fury[xtal]; + case DID0_CLASS_SANDSTORM: + return pllcfg_sandstorm[xtal]; + default: + hw_error("ssys_read: Unhandled class for PLLCFG read.\n"); + return 0; + } + } + case 0x070: /* RCC2 */ + return s->rcc2; + 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; + case 0x1e0: /* USER0 */ + return s->user0; + case 0x1e4: /* USER1 */ + return s->user1; + default: + hw_error("ssys_read: Bad offset 0x%x\n", (int)offset); + return 0; + } +} + +static bool ssys_use_rcc2(ssys_state *s) +{ + return (s->rcc2 >> 31) & 0x1; +} + +/* + * Caculate the sys. clock period in ms. + */ +static void ssys_calculate_system_clock(ssys_state *s) +{ + if (ssys_use_rcc2(s)) { + system_clock_scale = 5 * (((s->rcc2 >> 23) & 0x3f) + 1); + } else { + system_clock_scale = 5 * (((s->rcc >> 23) & 0xf) + 1); + } +} + +static void ssys_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + ssys_state *s = (ssys_state *)opaque; + + 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; + ssys_calculate_system_clock(s); + break; + case 0x070: /* RCC2 */ + if (ssys_board_class(s) == DID0_CLASS_SANDSTORM) { + break; + } + + if ((s->rcc2 & (1 << 13)) != 0 && (value & (1 << 13)) == 0) { + /* PLL enable. */ + s->int_status |= (1 << 6); + } + s->rcc2 = value; + ssys_calculate_system_clock(s); + 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: + hw_error("ssys_write: Bad offset 0x%x\n", (int)offset); + } + ssys_update(s); +} + +static const MemoryRegionOps ssys_ops = { + .read = ssys_read, + .write = ssys_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void ssys_reset(void *opaque) +{ + ssys_state *s = (ssys_state *)opaque; + + s->pborctl = 0x7ffd; + s->rcc = 0x078e3ac0; + + if (ssys_board_class(s) == DID0_CLASS_SANDSTORM) { + s->rcc2 = 0; + } else { + s->rcc2 = 0x07802810; + } + s->rcgc[0] = 1; + s->scgc[0] = 1; + s->dcgc[0] = 1; + ssys_calculate_system_clock(s); +} + +static int stellaris_sys_post_load(void *opaque, int version_id) +{ + ssys_state *s = opaque; + + ssys_calculate_system_clock(s); + + return 0; +} + +static const VMStateDescription vmstate_stellaris_sys = { + .name = "stellaris_sys", + .version_id = 2, + .minimum_version_id = 1, + .post_load = stellaris_sys_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32(pborctl, ssys_state), + VMSTATE_UINT32(ldopctl, ssys_state), + VMSTATE_UINT32(int_mask, ssys_state), + VMSTATE_UINT32(int_status, ssys_state), + VMSTATE_UINT32(resc, ssys_state), + VMSTATE_UINT32(rcc, ssys_state), + VMSTATE_UINT32_V(rcc2, ssys_state, 2), + VMSTATE_UINT32_ARRAY(rcgc, ssys_state, 3), + VMSTATE_UINT32_ARRAY(scgc, ssys_state, 3), + VMSTATE_UINT32_ARRAY(dcgc, ssys_state, 3), + VMSTATE_UINT32(clkvclr, ssys_state), + VMSTATE_UINT32(ldoarst, ssys_state), + VMSTATE_END_OF_LIST() + } +}; + +static int stellaris_sys_init(uint32_t base, qemu_irq irq, + stellaris_board_info * board, + uint8_t *macaddr) +{ + ssys_state *s; + + s = (ssys_state *)g_malloc0(sizeof(ssys_state)); + s->irq = irq; + s->board = board; + /* Most devices come preprogrammed with a MAC address in the user data. */ + s->user0 = macaddr[0] | (macaddr[1] << 8) | (macaddr[2] << 16); + s->user1 = macaddr[3] | (macaddr[4] << 8) | (macaddr[5] << 16); + + memory_region_init_io(&s->iomem, NULL, &ssys_ops, s, "ssys", 0x00001000); + memory_region_add_subregion(get_system_memory(), base, &s->iomem); + ssys_reset(s); + vmstate_register(NULL, -1, &vmstate_stellaris_sys, s); + return 0; +} + + +/* I2C controller. */ + +#define TYPE_STELLARIS_I2C "stellaris-i2c" +#define STELLARIS_I2C(obj) \ + OBJECT_CHECK(stellaris_i2c_state, (obj), TYPE_STELLARIS_I2C) + +typedef struct { + SysBusDevice parent_obj; + + I2CBus *bus; + qemu_irq irq; + MemoryRegion iomem; + 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 uint64_t stellaris_i2c_read(void *opaque, hwaddr offset, + unsigned size) +{ + stellaris_i2c_state *s = (stellaris_i2c_state *)opaque; + + 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: + hw_error("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, hwaddr offset, + uint64_t value, unsigned size) +{ + stellaris_i2c_state *s = (stellaris_i2c_state *)opaque; + + 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) + hw_error( + "stellaris_i2c_write: Loopback not implemented\n"); + if (value & 0x20) + hw_error( + "stellaris_i2c_write: Slave mode not implemented\n"); + s->mcr = value & 0x31; + break; + default: + hw_error("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 const MemoryRegionOps stellaris_i2c_ops = { + .read = stellaris_i2c_read, + .write = stellaris_i2c_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription vmstate_stellaris_i2c = { + .name = "stellaris_i2c", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(msa, stellaris_i2c_state), + VMSTATE_UINT32(mcs, stellaris_i2c_state), + VMSTATE_UINT32(mdr, stellaris_i2c_state), + VMSTATE_UINT32(mtpr, stellaris_i2c_state), + VMSTATE_UINT32(mimr, stellaris_i2c_state), + VMSTATE_UINT32(mris, stellaris_i2c_state), + VMSTATE_UINT32(mcr, stellaris_i2c_state), + VMSTATE_END_OF_LIST() + } +}; + +static int stellaris_i2c_init(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + stellaris_i2c_state *s = STELLARIS_I2C(dev); + I2CBus *bus; + + sysbus_init_irq(sbd, &s->irq); + bus = i2c_init_bus(dev, "i2c"); + s->bus = bus; + + memory_region_init_io(&s->iomem, OBJECT(s), &stellaris_i2c_ops, s, + "i2c", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + /* ??? For now we only implement the master interface. */ + stellaris_i2c_reset(s); + vmstate_register(dev, -1, &vmstate_stellaris_i2c, s); + return 0; +} + +/* 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 + +#define TYPE_STELLARIS_ADC "stellaris-adc" +#define STELLARIS_ADC(obj) \ + OBJECT_CHECK(stellaris_adc_state, (obj), TYPE_STELLARIS_ADC) + +typedef struct StellarisADCState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + 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]; + uint32_t noise; + qemu_irq irq[4]; +} 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; + + /* TODO: Real hardware has limited size FIFOs. We have a full 16 entry + FIFO fir each sequencer. */ + 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; + int n; + + for (n = 0; n < 4; n++) { + level = (s->ris & s->im & (1 << n)) != 0; + qemu_set_irq(s->irq[n], level); + } +} + +static void stellaris_adc_trigger(void *opaque, int irq, int level) +{ + stellaris_adc_state *s = (stellaris_adc_state *)opaque; + int n; + + for (n = 0; n < 4; n++) { + if ((s->actss & (1 << n)) == 0) { + continue; + } + + if (((s->emux >> (n * 4)) & 0xff) != 5) { + continue; + } + + /* Some applications use the ADC as a random number source, so introduce + some variation into the signal. */ + s->noise = s->noise * 314159 + 1; + /* ??? actual inputs not implemented. Return an arbitrary value. */ + stellaris_adc_fifo_write(s, n, 0x200 + ((s->noise >> 16) & 7)); + s->ris |= (1 << n); + 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 uint64_t stellaris_adc_read(void *opaque, hwaddr offset, + unsigned size) +{ + stellaris_adc_state *s = (stellaris_adc_state *)opaque; + + /* TODO: Implement this. */ + 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: + hw_error("strllaris_adc_read: Bad offset 0x%x\n", + (int)offset); + return 0; + } +} + +static void stellaris_adc_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + stellaris_adc_state *s = (stellaris_adc_state *)opaque; + + /* TODO: Implement this. */ + 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) { + hw_error("ADC: Unimplemented sequence %" PRIx64 "\n", + value); + } + s->ssctl[n] = value; + return; + default: + break; + } + } + switch (offset) { + case 0x00: /* ACTSS */ + s->actss = value & 0xf; + 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 */ + hw_error("Not implemented: ADC sample initiate\n"); + break; + case 0x30: /* SAC */ + s->sac = value; + break; + default: + hw_error("stellaris_adc_write: Bad offset 0x%x\n", (int)offset); + } + stellaris_adc_update(s); +} + +static const MemoryRegionOps stellaris_adc_ops = { + .read = stellaris_adc_read, + .write = stellaris_adc_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription vmstate_stellaris_adc = { + .name = "stellaris_adc", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(actss, stellaris_adc_state), + VMSTATE_UINT32(ris, stellaris_adc_state), + VMSTATE_UINT32(im, stellaris_adc_state), + VMSTATE_UINT32(emux, stellaris_adc_state), + VMSTATE_UINT32(ostat, stellaris_adc_state), + VMSTATE_UINT32(ustat, stellaris_adc_state), + VMSTATE_UINT32(sspri, stellaris_adc_state), + VMSTATE_UINT32(sac, stellaris_adc_state), + VMSTATE_UINT32(fifo[0].state, stellaris_adc_state), + VMSTATE_UINT32_ARRAY(fifo[0].data, stellaris_adc_state, 16), + VMSTATE_UINT32(ssmux[0], stellaris_adc_state), + VMSTATE_UINT32(ssctl[0], stellaris_adc_state), + VMSTATE_UINT32(fifo[1].state, stellaris_adc_state), + VMSTATE_UINT32_ARRAY(fifo[1].data, stellaris_adc_state, 16), + VMSTATE_UINT32(ssmux[1], stellaris_adc_state), + VMSTATE_UINT32(ssctl[1], stellaris_adc_state), + VMSTATE_UINT32(fifo[2].state, stellaris_adc_state), + VMSTATE_UINT32_ARRAY(fifo[2].data, stellaris_adc_state, 16), + VMSTATE_UINT32(ssmux[2], stellaris_adc_state), + VMSTATE_UINT32(ssctl[2], stellaris_adc_state), + VMSTATE_UINT32(fifo[3].state, stellaris_adc_state), + VMSTATE_UINT32_ARRAY(fifo[3].data, stellaris_adc_state, 16), + VMSTATE_UINT32(ssmux[3], stellaris_adc_state), + VMSTATE_UINT32(ssctl[3], stellaris_adc_state), + VMSTATE_UINT32(noise, stellaris_adc_state), + VMSTATE_END_OF_LIST() + } +}; + +static int stellaris_adc_init(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + stellaris_adc_state *s = STELLARIS_ADC(dev); + int n; + + for (n = 0; n < 4; n++) { + sysbus_init_irq(sbd, &s->irq[n]); + } + + memory_region_init_io(&s->iomem, OBJECT(s), &stellaris_adc_ops, s, + "adc", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + stellaris_adc_reset(s); + qdev_init_gpio_in(dev, stellaris_adc_trigger, 1); + vmstate_register(dev, -1, &vmstate_stellaris_adc, s); + return 0; +} + +/* Board init. */ +static stellaris_board_info stellaris_boards[] = { + { "LM3S811EVB", + 0, + 0x0032000e, + 0x001f001f, /* dc0 */ + 0x001132bf, + 0x01071013, + 0x3f0f01ff, + 0x0000001f, + BP_OLED_I2C + }, + { "LM3S6965EVB", + 0x10010002, + 0x1073402e, + 0x00ff007f, /* dc0 */ + 0x001133ff, + 0x030f5317, + 0x0f0f87ff, + 0x5000007f, + BP_OLED_SSI | BP_GAMEPAD + } +}; + +static void stellaris_init(const char *kernel_filename, const char *cpu_model, + 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; + DeviceState *gpio_dev[7]; + qemu_irq gpio_in[7][8]; + qemu_irq gpio_out[7][8]; + qemu_irq adc; + int sram_size; + int flash_size; + I2CBus *i2c; + DeviceState *dev; + int i; + int j; + + MemoryRegion *sram = g_new(MemoryRegion, 1); + MemoryRegion *flash = g_new(MemoryRegion, 1); + MemoryRegion *system_memory = get_system_memory(); + + flash_size = (((board->dc0 & 0xffff) + 1) << 1) * 1024; + sram_size = ((board->dc0 >> 18) + 1) * 1024; + + /* Flash programming is done via the SCU, so pretend it is ROM. */ + memory_region_init_ram(flash, NULL, "stellaris.flash", flash_size, + &error_abort); + vmstate_register_ram_global(flash); + memory_region_set_readonly(flash, true); + memory_region_add_subregion(system_memory, 0, flash); + + memory_region_init_ram(sram, NULL, "stellaris.sram", sram_size, + &error_abort); + vmstate_register_ram_global(sram); + memory_region_add_subregion(system_memory, 0x20000000, sram); + + pic = armv7m_init(system_memory, flash_size, NUM_IRQ_LINES, + kernel_filename, cpu_model); + + if (board->dc1 & (1 << 16)) { + dev = sysbus_create_varargs(TYPE_STELLARIS_ADC, 0x40038000, + pic[14], pic[15], pic[16], pic[17], NULL); + adc = qdev_get_gpio_in(dev, 0); + } else { + adc = NULL; + } + for (i = 0; i < 4; i++) { + if (board->dc2 & (0x10000 << i)) { + dev = sysbus_create_simple(TYPE_STELLARIS_GPTM, + 0x40030000 + i * 0x1000, + pic[timer_irq[i]]); + /* TODO: This is incorrect, but we get away with it because + the ADC output is only ever pulsed. */ + qdev_connect_gpio_out(dev, 0, adc); + } + } + + stellaris_sys_init(0x400fe000, pic[28], board, nd_table[0].macaddr.a); + + for (i = 0; i < 7; i++) { + if (board->dc4 & (1 << i)) { + gpio_dev[i] = sysbus_create_simple("pl061_luminary", gpio_addr[i], + pic[gpio_irq[i]]); + for (j = 0; j < 8; j++) { + gpio_in[i][j] = qdev_get_gpio_in(gpio_dev[i], j); + gpio_out[i][j] = NULL; + } + } + } + + if (board->dc2 & (1 << 12)) { + dev = sysbus_create_simple(TYPE_STELLARIS_I2C, 0x40020000, pic[8]); + i2c = (I2CBus *)qdev_get_child_bus(dev, "i2c"); + if (board->peripherals & BP_OLED_I2C) { + i2c_create_slave(i2c, "ssd0303", 0x3d); + } + } + + for (i = 0; i < 4; i++) { + if (board->dc2 & (1 << i)) { + sysbus_create_simple("pl011_luminary", 0x4000c000 + i * 0x1000, + pic[uart_irq[i]]); + } + } + if (board->dc2 & (1 << 4)) { + dev = sysbus_create_simple("pl022", 0x40008000, pic[7]); + if (board->peripherals & BP_OLED_SSI) { + void *bus; + DeviceState *sddev; + DeviceState *ssddev; + + /* Some boards have both an OLED controller and SD card connected to + * the same SSI port, with the SD card chip select connected to a + * GPIO pin. Technically the OLED chip select is connected to the + * SSI Fss pin. We do not bother emulating that as both devices + * should never be selected simultaneously, and our OLED controller + * ignores stray 0xff commands that occur when deselecting the SD + * card. + */ + bus = qdev_get_child_bus(dev, "ssi"); + + sddev = ssi_create_slave(bus, "ssi-sd"); + ssddev = ssi_create_slave(bus, "ssd0323"); + gpio_out[GPIO_D][0] = qemu_irq_split( + qdev_get_gpio_in_named(sddev, SSI_GPIO_CS, 0), + qdev_get_gpio_in_named(ssddev, SSI_GPIO_CS, 0)); + gpio_out[GPIO_C][7] = qdev_get_gpio_in(ssddev, 0); + + /* Make sure the select pin is high. */ + qemu_irq_raise(gpio_out[GPIO_D][0]); + } + } + if (board->dc4 & (1 << 28)) { + DeviceState *enet; + + qemu_check_nic_model(&nd_table[0], "stellaris"); + + enet = qdev_create(NULL, "stellaris_enet"); + qdev_set_nic_properties(enet, &nd_table[0]); + qdev_init_nofail(enet); + sysbus_mmio_map(SYS_BUS_DEVICE(enet), 0, 0x40048000); + sysbus_connect_irq(SYS_BUS_DEVICE(enet), 0, pic[42]); + } + if (board->peripherals & BP_GAMEPAD) { + qemu_irq gpad_irq[5]; + static const int gpad_keycode[5] = { 0xc8, 0xd0, 0xcb, 0xcd, 0x1d }; + + gpad_irq[0] = qemu_irq_invert(gpio_in[GPIO_E][0]); /* up */ + gpad_irq[1] = qemu_irq_invert(gpio_in[GPIO_E][1]); /* down */ + gpad_irq[2] = qemu_irq_invert(gpio_in[GPIO_E][2]); /* left */ + gpad_irq[3] = qemu_irq_invert(gpio_in[GPIO_E][3]); /* right */ + gpad_irq[4] = qemu_irq_invert(gpio_in[GPIO_F][1]); /* select */ + + stellaris_gamepad_init(5, gpad_irq, gpad_keycode); + } + for (i = 0; i < 7; i++) { + if (board->dc4 & (1 << i)) { + for (j = 0; j < 8; j++) { + if (gpio_out[i][j]) { + qdev_connect_gpio_out(gpio_dev[i], j, gpio_out[i][j]); + } + } + } + } +} + +/* FIXME: Figure out how to generate these from stellaris_boards. */ +static void lm3s811evb_init(MachineState *machine) +{ + const char *cpu_model = machine->cpu_model; + const char *kernel_filename = machine->kernel_filename; + stellaris_init(kernel_filename, cpu_model, &stellaris_boards[0]); +} + +static void lm3s6965evb_init(MachineState *machine) +{ + const char *cpu_model = machine->cpu_model; + const char *kernel_filename = machine->kernel_filename; + stellaris_init(kernel_filename, cpu_model, &stellaris_boards[1]); +} + +static QEMUMachine lm3s811evb_machine = { + .name = "lm3s811evb", + .desc = "Stellaris LM3S811EVB", + .init = lm3s811evb_init, +}; + +static QEMUMachine lm3s6965evb_machine = { + .name = "lm3s6965evb", + .desc = "Stellaris LM3S6965EVB", + .init = lm3s6965evb_init, +}; + +static void stellaris_machine_init(void) +{ + qemu_register_machine(&lm3s811evb_machine); + qemu_register_machine(&lm3s6965evb_machine); +} + +machine_init(stellaris_machine_init); + +static void stellaris_i2c_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + + sdc->init = stellaris_i2c_init; +} + +static const TypeInfo stellaris_i2c_info = { + .name = TYPE_STELLARIS_I2C, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(stellaris_i2c_state), + .class_init = stellaris_i2c_class_init, +}; + +static void stellaris_gptm_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + + sdc->init = stellaris_gptm_init; +} + +static const TypeInfo stellaris_gptm_info = { + .name = TYPE_STELLARIS_GPTM, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(gptm_state), + .class_init = stellaris_gptm_class_init, +}; + +static void stellaris_adc_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + + sdc->init = stellaris_adc_init; +} + +static const TypeInfo stellaris_adc_info = { + .name = TYPE_STELLARIS_ADC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(stellaris_adc_state), + .class_init = stellaris_adc_class_init, +}; + +static void stellaris_register_types(void) +{ + type_register_static(&stellaris_i2c_info); + type_register_static(&stellaris_gptm_info); + type_register_static(&stellaris_adc_info); +} + +type_init(stellaris_register_types) diff --git a/qemu/hw/arm/stm32f205_soc.c b/qemu/hw/arm/stm32f205_soc.c new file mode 100644 index 000000000..0f3bdc77b --- /dev/null +++ b/qemu/hw/arm/stm32f205_soc.c @@ -0,0 +1,160 @@ +/* + * STM32F205 SoC + * + * Copyright (c) 2014 Alistair Francis <alistair@alistair23.me> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "hw/arm/arm.h" +#include "exec/address-spaces.h" +#include "hw/arm/stm32f205_soc.h" + +/* At the moment only Timer 2 to 5 are modelled */ +static const uint32_t timer_addr[STM_NUM_TIMERS] = { 0x40000000, 0x40000400, + 0x40000800, 0x40000C00 }; +static const uint32_t usart_addr[STM_NUM_USARTS] = { 0x40011000, 0x40004400, + 0x40004800, 0x40004C00, 0x40005000, 0x40011400 }; + +static const int timer_irq[STM_NUM_TIMERS] = {28, 29, 30, 50}; +static const int usart_irq[STM_NUM_USARTS] = {37, 38, 39, 52, 53, 71}; + +static void stm32f205_soc_initfn(Object *obj) +{ + STM32F205State *s = STM32F205_SOC(obj); + int i; + + object_initialize(&s->syscfg, sizeof(s->syscfg), TYPE_STM32F2XX_SYSCFG); + qdev_set_parent_bus(DEVICE(&s->syscfg), sysbus_get_default()); + + for (i = 0; i < STM_NUM_USARTS; i++) { + object_initialize(&s->usart[i], sizeof(s->usart[i]), + TYPE_STM32F2XX_USART); + qdev_set_parent_bus(DEVICE(&s->usart[i]), sysbus_get_default()); + } + + for (i = 0; i < STM_NUM_TIMERS; i++) { + object_initialize(&s->timer[i], sizeof(s->timer[i]), + TYPE_STM32F2XX_TIMER); + qdev_set_parent_bus(DEVICE(&s->timer[i]), sysbus_get_default()); + } +} + +static void stm32f205_soc_realize(DeviceState *dev_soc, Error **errp) +{ + STM32F205State *s = STM32F205_SOC(dev_soc); + DeviceState *syscfgdev, *usartdev, *timerdev; + SysBusDevice *syscfgbusdev, *usartbusdev, *timerbusdev; + qemu_irq *pic; + Error *err = NULL; + int i; + + MemoryRegion *system_memory = get_system_memory(); + MemoryRegion *sram = g_new(MemoryRegion, 1); + MemoryRegion *flash = g_new(MemoryRegion, 1); + MemoryRegion *flash_alias = g_new(MemoryRegion, 1); + + memory_region_init_ram(flash, NULL, "STM32F205.flash", FLASH_SIZE, + &error_abort); + memory_region_init_alias(flash_alias, NULL, "STM32F205.flash.alias", + flash, 0, FLASH_SIZE); + + vmstate_register_ram_global(flash); + + memory_region_set_readonly(flash, true); + memory_region_set_readonly(flash_alias, true); + + memory_region_add_subregion(system_memory, FLASH_BASE_ADDRESS, flash); + memory_region_add_subregion(system_memory, 0, flash_alias); + + memory_region_init_ram(sram, NULL, "STM32F205.sram", SRAM_SIZE, + &error_abort); + vmstate_register_ram_global(sram); + memory_region_add_subregion(system_memory, SRAM_BASE_ADDRESS, sram); + + pic = armv7m_init(get_system_memory(), FLASH_SIZE, 96, + s->kernel_filename, s->cpu_model); + + /* System configuration controller */ + syscfgdev = DEVICE(&s->syscfg); + object_property_set_bool(OBJECT(&s->syscfg), true, "realized", &err); + if (err != NULL) { + error_propagate(errp, err); + return; + } + syscfgbusdev = SYS_BUS_DEVICE(syscfgdev); + sysbus_mmio_map(syscfgbusdev, 0, 0x40013800); + sysbus_connect_irq(syscfgbusdev, 0, pic[71]); + + /* Attach UART (uses USART registers) and USART controllers */ + for (i = 0; i < STM_NUM_USARTS; i++) { + usartdev = DEVICE(&(s->usart[i])); + object_property_set_bool(OBJECT(&s->usart[i]), true, "realized", &err); + if (err != NULL) { + error_propagate(errp, err); + return; + } + usartbusdev = SYS_BUS_DEVICE(usartdev); + sysbus_mmio_map(usartbusdev, 0, usart_addr[i]); + sysbus_connect_irq(usartbusdev, 0, pic[usart_irq[i]]); + } + + /* Timer 2 to 5 */ + for (i = 0; i < STM_NUM_TIMERS; i++) { + timerdev = DEVICE(&(s->timer[i])); + qdev_prop_set_uint64(timerdev, "clock-frequency", 1000000000); + object_property_set_bool(OBJECT(&s->timer[i]), true, "realized", &err); + if (err != NULL) { + error_propagate(errp, err); + return; + } + timerbusdev = SYS_BUS_DEVICE(timerdev); + sysbus_mmio_map(timerbusdev, 0, timer_addr[i]); + sysbus_connect_irq(timerbusdev, 0, pic[timer_irq[i]]); + } +} + +static Property stm32f205_soc_properties[] = { + DEFINE_PROP_STRING("kernel-filename", STM32F205State, kernel_filename), + DEFINE_PROP_STRING("cpu-model", STM32F205State, cpu_model), + DEFINE_PROP_END_OF_LIST(), +}; + +static void stm32f205_soc_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = stm32f205_soc_realize; + dc->props = stm32f205_soc_properties; +} + +static const TypeInfo stm32f205_soc_info = { + .name = TYPE_STM32F205_SOC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(STM32F205State), + .instance_init = stm32f205_soc_initfn, + .class_init = stm32f205_soc_class_init, +}; + +static void stm32f205_soc_types(void) +{ + type_register_static(&stm32f205_soc_info); +} + +type_init(stm32f205_soc_types) diff --git a/qemu/hw/arm/strongarm.c b/qemu/hw/arm/strongarm.c new file mode 100644 index 000000000..da9fc1d51 --- /dev/null +++ b/qemu/hw/arm/strongarm.c @@ -0,0 +1,1659 @@ +/* + * StrongARM SA-1100/SA-1110 emulation + * + * Copyright (C) 2011 Dmitry Eremin-Solenikov + * + * Largely based on StrongARM emulation: + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski <balrog@zabor.org> + * + * UART code based on QEMU 16550A UART emulation + * Copyright (c) 2003-2004 Fabrice Bellard + * Copyright (c) 2008 Citrix Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "hw/boards.h" +#include "hw/sysbus.h" +#include "strongarm.h" +#include "qemu/error-report.h" +#include "hw/arm/arm.h" +#include "sysemu/char.h" +#include "sysemu/sysemu.h" +#include "hw/ssi.h" + +//#define DEBUG + +/* + TODO + - Implement cp15, c14 ? + - Implement cp15, c15 !!! (idle used in L) + - Implement idle mode handling/DIM + - Implement sleep mode/Wake sources + - Implement reset control + - Implement memory control regs + - PCMCIA handling + - Maybe support MBGNT/MBREQ + - DMA channels + - GPCLK + - IrDA + - MCP + - Enhance UART with modem signals + */ + +#ifdef DEBUG +# define DPRINTF(format, ...) printf(format , ## __VA_ARGS__) +#else +# define DPRINTF(format, ...) do { } while (0) +#endif + +static struct { + hwaddr io_base; + int irq; +} sa_serial[] = { + { 0x80010000, SA_PIC_UART1 }, + { 0x80030000, SA_PIC_UART2 }, + { 0x80050000, SA_PIC_UART3 }, + { 0, 0 } +}; + +/* Interrupt Controller */ + +#define TYPE_STRONGARM_PIC "strongarm_pic" +#define STRONGARM_PIC(obj) \ + OBJECT_CHECK(StrongARMPICState, (obj), TYPE_STRONGARM_PIC) + +typedef struct StrongARMPICState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + qemu_irq irq; + qemu_irq fiq; + + uint32_t pending; + uint32_t enabled; + uint32_t is_fiq; + uint32_t int_idle; +} StrongARMPICState; + +#define ICIP 0x00 +#define ICMR 0x04 +#define ICLR 0x08 +#define ICFP 0x10 +#define ICPR 0x20 +#define ICCR 0x0c + +#define SA_PIC_SRCS 32 + + +static void strongarm_pic_update(void *opaque) +{ + StrongARMPICState *s = opaque; + + /* FIXME: reflect DIM */ + qemu_set_irq(s->fiq, s->pending & s->enabled & s->is_fiq); + qemu_set_irq(s->irq, s->pending & s->enabled & ~s->is_fiq); +} + +static void strongarm_pic_set_irq(void *opaque, int irq, int level) +{ + StrongARMPICState *s = opaque; + + if (level) { + s->pending |= 1 << irq; + } else { + s->pending &= ~(1 << irq); + } + + strongarm_pic_update(s); +} + +static uint64_t strongarm_pic_mem_read(void *opaque, hwaddr offset, + unsigned size) +{ + StrongARMPICState *s = opaque; + + switch (offset) { + case ICIP: + return s->pending & ~s->is_fiq & s->enabled; + case ICMR: + return s->enabled; + case ICLR: + return s->is_fiq; + case ICCR: + return s->int_idle == 0; + case ICFP: + return s->pending & s->is_fiq & s->enabled; + case ICPR: + return s->pending; + default: + printf("%s: Bad register offset 0x" TARGET_FMT_plx "\n", + __func__, offset); + return 0; + } +} + +static void strongarm_pic_mem_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + StrongARMPICState *s = opaque; + + switch (offset) { + case ICMR: + s->enabled = value; + break; + case ICLR: + s->is_fiq = value; + break; + case ICCR: + s->int_idle = (value & 1) ? 0 : ~0; + break; + default: + printf("%s: Bad register offset 0x" TARGET_FMT_plx "\n", + __func__, offset); + break; + } + strongarm_pic_update(s); +} + +static const MemoryRegionOps strongarm_pic_ops = { + .read = strongarm_pic_mem_read, + .write = strongarm_pic_mem_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int strongarm_pic_initfn(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + StrongARMPICState *s = STRONGARM_PIC(dev); + + qdev_init_gpio_in(dev, strongarm_pic_set_irq, SA_PIC_SRCS); + memory_region_init_io(&s->iomem, OBJECT(s), &strongarm_pic_ops, s, + "pic", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); + sysbus_init_irq(sbd, &s->fiq); + + return 0; +} + +static int strongarm_pic_post_load(void *opaque, int version_id) +{ + strongarm_pic_update(opaque); + return 0; +} + +static VMStateDescription vmstate_strongarm_pic_regs = { + .name = "strongarm_pic", + .version_id = 0, + .minimum_version_id = 0, + .post_load = strongarm_pic_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32(pending, StrongARMPICState), + VMSTATE_UINT32(enabled, StrongARMPICState), + VMSTATE_UINT32(is_fiq, StrongARMPICState), + VMSTATE_UINT32(int_idle, StrongARMPICState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void strongarm_pic_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = strongarm_pic_initfn; + dc->desc = "StrongARM PIC"; + dc->vmsd = &vmstate_strongarm_pic_regs; +} + +static const TypeInfo strongarm_pic_info = { + .name = TYPE_STRONGARM_PIC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(StrongARMPICState), + .class_init = strongarm_pic_class_init, +}; + +/* Real-Time Clock */ +#define RTAR 0x00 /* RTC Alarm register */ +#define RCNR 0x04 /* RTC Counter register */ +#define RTTR 0x08 /* RTC Timer Trim register */ +#define RTSR 0x10 /* RTC Status register */ + +#define RTSR_AL (1 << 0) /* RTC Alarm detected */ +#define RTSR_HZ (1 << 1) /* RTC 1Hz detected */ +#define RTSR_ALE (1 << 2) /* RTC Alarm enable */ +#define RTSR_HZE (1 << 3) /* RTC 1Hz enable */ + +/* 16 LSB of RTTR are clockdiv for internal trim logic, + * trim delete isn't emulated, so + * f = 32 768 / (RTTR_trim + 1) */ + +#define TYPE_STRONGARM_RTC "strongarm-rtc" +#define STRONGARM_RTC(obj) \ + OBJECT_CHECK(StrongARMRTCState, (obj), TYPE_STRONGARM_RTC) + +typedef struct StrongARMRTCState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + uint32_t rttr; + uint32_t rtsr; + uint32_t rtar; + uint32_t last_rcnr; + int64_t last_hz; + QEMUTimer *rtc_alarm; + QEMUTimer *rtc_hz; + qemu_irq rtc_irq; + qemu_irq rtc_hz_irq; +} StrongARMRTCState; + +static inline void strongarm_rtc_int_update(StrongARMRTCState *s) +{ + qemu_set_irq(s->rtc_irq, s->rtsr & RTSR_AL); + qemu_set_irq(s->rtc_hz_irq, s->rtsr & RTSR_HZ); +} + +static void strongarm_rtc_hzupdate(StrongARMRTCState *s) +{ + int64_t rt = qemu_clock_get_ms(rtc_clock); + s->last_rcnr += ((rt - s->last_hz) << 15) / + (1000 * ((s->rttr & 0xffff) + 1)); + s->last_hz = rt; +} + +static inline void strongarm_rtc_timer_update(StrongARMRTCState *s) +{ + if ((s->rtsr & RTSR_HZE) && !(s->rtsr & RTSR_HZ)) { + timer_mod(s->rtc_hz, s->last_hz + 1000); + } else { + timer_del(s->rtc_hz); + } + + if ((s->rtsr & RTSR_ALE) && !(s->rtsr & RTSR_AL)) { + timer_mod(s->rtc_alarm, s->last_hz + + (((s->rtar - s->last_rcnr) * 1000 * + ((s->rttr & 0xffff) + 1)) >> 15)); + } else { + timer_del(s->rtc_alarm); + } +} + +static inline void strongarm_rtc_alarm_tick(void *opaque) +{ + StrongARMRTCState *s = opaque; + s->rtsr |= RTSR_AL; + strongarm_rtc_timer_update(s); + strongarm_rtc_int_update(s); +} + +static inline void strongarm_rtc_hz_tick(void *opaque) +{ + StrongARMRTCState *s = opaque; + s->rtsr |= RTSR_HZ; + strongarm_rtc_timer_update(s); + strongarm_rtc_int_update(s); +} + +static uint64_t strongarm_rtc_read(void *opaque, hwaddr addr, + unsigned size) +{ + StrongARMRTCState *s = opaque; + + switch (addr) { + case RTTR: + return s->rttr; + case RTSR: + return s->rtsr; + case RTAR: + return s->rtar; + case RCNR: + return s->last_rcnr + + ((qemu_clock_get_ms(rtc_clock) - s->last_hz) << 15) / + (1000 * ((s->rttr & 0xffff) + 1)); + default: + printf("%s: Bad register 0x" TARGET_FMT_plx "\n", __func__, addr); + return 0; + } +} + +static void strongarm_rtc_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + StrongARMRTCState *s = opaque; + uint32_t old_rtsr; + + switch (addr) { + case RTTR: + strongarm_rtc_hzupdate(s); + s->rttr = value; + strongarm_rtc_timer_update(s); + break; + + case RTSR: + old_rtsr = s->rtsr; + s->rtsr = (value & (RTSR_ALE | RTSR_HZE)) | + (s->rtsr & ~(value & (RTSR_AL | RTSR_HZ))); + + if (s->rtsr != old_rtsr) { + strongarm_rtc_timer_update(s); + } + + strongarm_rtc_int_update(s); + break; + + case RTAR: + s->rtar = value; + strongarm_rtc_timer_update(s); + break; + + case RCNR: + strongarm_rtc_hzupdate(s); + s->last_rcnr = value; + strongarm_rtc_timer_update(s); + break; + + default: + printf("%s: Bad register 0x" TARGET_FMT_plx "\n", __func__, addr); + } +} + +static const MemoryRegionOps strongarm_rtc_ops = { + .read = strongarm_rtc_read, + .write = strongarm_rtc_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int strongarm_rtc_init(SysBusDevice *dev) +{ + StrongARMRTCState *s = STRONGARM_RTC(dev); + struct tm tm; + + s->rttr = 0x0; + s->rtsr = 0; + + qemu_get_timedate(&tm, 0); + + s->last_rcnr = (uint32_t) mktimegm(&tm); + s->last_hz = qemu_clock_get_ms(rtc_clock); + + s->rtc_alarm = timer_new_ms(rtc_clock, strongarm_rtc_alarm_tick, s); + s->rtc_hz = timer_new_ms(rtc_clock, strongarm_rtc_hz_tick, s); + + sysbus_init_irq(dev, &s->rtc_irq); + sysbus_init_irq(dev, &s->rtc_hz_irq); + + memory_region_init_io(&s->iomem, OBJECT(s), &strongarm_rtc_ops, s, + "rtc", 0x10000); + sysbus_init_mmio(dev, &s->iomem); + + return 0; +} + +static void strongarm_rtc_pre_save(void *opaque) +{ + StrongARMRTCState *s = opaque; + + strongarm_rtc_hzupdate(s); +} + +static int strongarm_rtc_post_load(void *opaque, int version_id) +{ + StrongARMRTCState *s = opaque; + + strongarm_rtc_timer_update(s); + strongarm_rtc_int_update(s); + + return 0; +} + +static const VMStateDescription vmstate_strongarm_rtc_regs = { + .name = "strongarm-rtc", + .version_id = 0, + .minimum_version_id = 0, + .pre_save = strongarm_rtc_pre_save, + .post_load = strongarm_rtc_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32(rttr, StrongARMRTCState), + VMSTATE_UINT32(rtsr, StrongARMRTCState), + VMSTATE_UINT32(rtar, StrongARMRTCState), + VMSTATE_UINT32(last_rcnr, StrongARMRTCState), + VMSTATE_INT64(last_hz, StrongARMRTCState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void strongarm_rtc_sysbus_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = strongarm_rtc_init; + dc->desc = "StrongARM RTC Controller"; + dc->vmsd = &vmstate_strongarm_rtc_regs; +} + +static const TypeInfo strongarm_rtc_sysbus_info = { + .name = TYPE_STRONGARM_RTC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(StrongARMRTCState), + .class_init = strongarm_rtc_sysbus_class_init, +}; + +/* GPIO */ +#define GPLR 0x00 +#define GPDR 0x04 +#define GPSR 0x08 +#define GPCR 0x0c +#define GRER 0x10 +#define GFER 0x14 +#define GEDR 0x18 +#define GAFR 0x1c + +#define TYPE_STRONGARM_GPIO "strongarm-gpio" +#define STRONGARM_GPIO(obj) \ + OBJECT_CHECK(StrongARMGPIOInfo, (obj), TYPE_STRONGARM_GPIO) + +typedef struct StrongARMGPIOInfo StrongARMGPIOInfo; +struct StrongARMGPIOInfo { + SysBusDevice busdev; + MemoryRegion iomem; + qemu_irq handler[28]; + qemu_irq irqs[11]; + qemu_irq irqX; + + uint32_t ilevel; + uint32_t olevel; + uint32_t dir; + uint32_t rising; + uint32_t falling; + uint32_t status; + uint32_t gafr; + + uint32_t prev_level; +}; + + +static void strongarm_gpio_irq_update(StrongARMGPIOInfo *s) +{ + int i; + for (i = 0; i < 11; i++) { + qemu_set_irq(s->irqs[i], s->status & (1 << i)); + } + + qemu_set_irq(s->irqX, (s->status & ~0x7ff)); +} + +static void strongarm_gpio_set(void *opaque, int line, int level) +{ + StrongARMGPIOInfo *s = opaque; + uint32_t mask; + + mask = 1 << line; + + if (level) { + s->status |= s->rising & mask & + ~s->ilevel & ~s->dir; + s->ilevel |= mask; + } else { + s->status |= s->falling & mask & + s->ilevel & ~s->dir; + s->ilevel &= ~mask; + } + + if (s->status & mask) { + strongarm_gpio_irq_update(s); + } +} + +static void strongarm_gpio_handler_update(StrongARMGPIOInfo *s) +{ + uint32_t level, diff; + int bit; + + level = s->olevel & s->dir; + + for (diff = s->prev_level ^ level; diff; diff ^= 1 << bit) { + bit = ctz32(diff); + qemu_set_irq(s->handler[bit], (level >> bit) & 1); + } + + s->prev_level = level; +} + +static uint64_t strongarm_gpio_read(void *opaque, hwaddr offset, + unsigned size) +{ + StrongARMGPIOInfo *s = opaque; + + switch (offset) { + case GPDR: /* GPIO Pin-Direction registers */ + return s->dir; + + case GPSR: /* GPIO Pin-Output Set registers */ + qemu_log_mask(LOG_GUEST_ERROR, + "strongarm GPIO: read from write only register GPSR\n"); + return 0; + + case GPCR: /* GPIO Pin-Output Clear registers */ + qemu_log_mask(LOG_GUEST_ERROR, + "strongarm GPIO: read from write only register GPCR\n"); + return 0; + + case GRER: /* GPIO Rising-Edge Detect Enable registers */ + return s->rising; + + case GFER: /* GPIO Falling-Edge Detect Enable registers */ + return s->falling; + + case GAFR: /* GPIO Alternate Function registers */ + return s->gafr; + + case GPLR: /* GPIO Pin-Level registers */ + return (s->olevel & s->dir) | + (s->ilevel & ~s->dir); + + case GEDR: /* GPIO Edge Detect Status registers */ + return s->status; + + default: + printf("%s: Bad offset 0x" TARGET_FMT_plx "\n", __func__, offset); + } + + return 0; +} + +static void strongarm_gpio_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + StrongARMGPIOInfo *s = opaque; + + switch (offset) { + case GPDR: /* GPIO Pin-Direction registers */ + s->dir = value; + strongarm_gpio_handler_update(s); + break; + + case GPSR: /* GPIO Pin-Output Set registers */ + s->olevel |= value; + strongarm_gpio_handler_update(s); + break; + + case GPCR: /* GPIO Pin-Output Clear registers */ + s->olevel &= ~value; + strongarm_gpio_handler_update(s); + break; + + case GRER: /* GPIO Rising-Edge Detect Enable registers */ + s->rising = value; + break; + + case GFER: /* GPIO Falling-Edge Detect Enable registers */ + s->falling = value; + break; + + case GAFR: /* GPIO Alternate Function registers */ + s->gafr = value; + break; + + case GEDR: /* GPIO Edge Detect Status registers */ + s->status &= ~value; + strongarm_gpio_irq_update(s); + break; + + default: + printf("%s: Bad offset 0x" TARGET_FMT_plx "\n", __func__, offset); + } +} + +static const MemoryRegionOps strongarm_gpio_ops = { + .read = strongarm_gpio_read, + .write = strongarm_gpio_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static DeviceState *strongarm_gpio_init(hwaddr base, + DeviceState *pic) +{ + DeviceState *dev; + int i; + + dev = qdev_create(NULL, TYPE_STRONGARM_GPIO); + qdev_init_nofail(dev); + + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base); + for (i = 0; i < 12; i++) + sysbus_connect_irq(SYS_BUS_DEVICE(dev), i, + qdev_get_gpio_in(pic, SA_PIC_GPIO0_EDGE + i)); + + return dev; +} + +static int strongarm_gpio_initfn(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + StrongARMGPIOInfo *s = STRONGARM_GPIO(dev); + int i; + + qdev_init_gpio_in(dev, strongarm_gpio_set, 28); + qdev_init_gpio_out(dev, s->handler, 28); + + memory_region_init_io(&s->iomem, OBJECT(s), &strongarm_gpio_ops, s, + "gpio", 0x1000); + + sysbus_init_mmio(sbd, &s->iomem); + for (i = 0; i < 11; i++) { + sysbus_init_irq(sbd, &s->irqs[i]); + } + sysbus_init_irq(sbd, &s->irqX); + + return 0; +} + +static const VMStateDescription vmstate_strongarm_gpio_regs = { + .name = "strongarm-gpio", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT32(ilevel, StrongARMGPIOInfo), + VMSTATE_UINT32(olevel, StrongARMGPIOInfo), + VMSTATE_UINT32(dir, StrongARMGPIOInfo), + VMSTATE_UINT32(rising, StrongARMGPIOInfo), + VMSTATE_UINT32(falling, StrongARMGPIOInfo), + VMSTATE_UINT32(status, StrongARMGPIOInfo), + VMSTATE_UINT32(gafr, StrongARMGPIOInfo), + VMSTATE_UINT32(prev_level, StrongARMGPIOInfo), + VMSTATE_END_OF_LIST(), + }, +}; + +static void strongarm_gpio_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = strongarm_gpio_initfn; + dc->desc = "StrongARM GPIO controller"; + dc->vmsd = &vmstate_strongarm_gpio_regs; +} + +static const TypeInfo strongarm_gpio_info = { + .name = TYPE_STRONGARM_GPIO, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(StrongARMGPIOInfo), + .class_init = strongarm_gpio_class_init, +}; + +/* Peripheral Pin Controller */ +#define PPDR 0x00 +#define PPSR 0x04 +#define PPAR 0x08 +#define PSDR 0x0c +#define PPFR 0x10 + +#define TYPE_STRONGARM_PPC "strongarm-ppc" +#define STRONGARM_PPC(obj) \ + OBJECT_CHECK(StrongARMPPCInfo, (obj), TYPE_STRONGARM_PPC) + +typedef struct StrongARMPPCInfo StrongARMPPCInfo; +struct StrongARMPPCInfo { + SysBusDevice parent_obj; + + MemoryRegion iomem; + qemu_irq handler[28]; + + uint32_t ilevel; + uint32_t olevel; + uint32_t dir; + uint32_t ppar; + uint32_t psdr; + uint32_t ppfr; + + uint32_t prev_level; +}; + +static void strongarm_ppc_set(void *opaque, int line, int level) +{ + StrongARMPPCInfo *s = opaque; + + if (level) { + s->ilevel |= 1 << line; + } else { + s->ilevel &= ~(1 << line); + } +} + +static void strongarm_ppc_handler_update(StrongARMPPCInfo *s) +{ + uint32_t level, diff; + int bit; + + level = s->olevel & s->dir; + + for (diff = s->prev_level ^ level; diff; diff ^= 1 << bit) { + bit = ctz32(diff); + qemu_set_irq(s->handler[bit], (level >> bit) & 1); + } + + s->prev_level = level; +} + +static uint64_t strongarm_ppc_read(void *opaque, hwaddr offset, + unsigned size) +{ + StrongARMPPCInfo *s = opaque; + + switch (offset) { + case PPDR: /* PPC Pin Direction registers */ + return s->dir | ~0x3fffff; + + case PPSR: /* PPC Pin State registers */ + return (s->olevel & s->dir) | + (s->ilevel & ~s->dir) | + ~0x3fffff; + + case PPAR: + return s->ppar | ~0x41000; + + case PSDR: + return s->psdr; + + case PPFR: + return s->ppfr | ~0x7f001; + + default: + printf("%s: Bad offset 0x" TARGET_FMT_plx "\n", __func__, offset); + } + + return 0; +} + +static void strongarm_ppc_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + StrongARMPPCInfo *s = opaque; + + switch (offset) { + case PPDR: /* PPC Pin Direction registers */ + s->dir = value & 0x3fffff; + strongarm_ppc_handler_update(s); + break; + + case PPSR: /* PPC Pin State registers */ + s->olevel = value & s->dir & 0x3fffff; + strongarm_ppc_handler_update(s); + break; + + case PPAR: + s->ppar = value & 0x41000; + break; + + case PSDR: + s->psdr = value & 0x3fffff; + break; + + case PPFR: + s->ppfr = value & 0x7f001; + break; + + default: + printf("%s: Bad offset 0x" TARGET_FMT_plx "\n", __func__, offset); + } +} + +static const MemoryRegionOps strongarm_ppc_ops = { + .read = strongarm_ppc_read, + .write = strongarm_ppc_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int strongarm_ppc_init(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + StrongARMPPCInfo *s = STRONGARM_PPC(dev); + + qdev_init_gpio_in(dev, strongarm_ppc_set, 22); + qdev_init_gpio_out(dev, s->handler, 22); + + memory_region_init_io(&s->iomem, OBJECT(s), &strongarm_ppc_ops, s, + "ppc", 0x1000); + + sysbus_init_mmio(sbd, &s->iomem); + + return 0; +} + +static const VMStateDescription vmstate_strongarm_ppc_regs = { + .name = "strongarm-ppc", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT32(ilevel, StrongARMPPCInfo), + VMSTATE_UINT32(olevel, StrongARMPPCInfo), + VMSTATE_UINT32(dir, StrongARMPPCInfo), + VMSTATE_UINT32(ppar, StrongARMPPCInfo), + VMSTATE_UINT32(psdr, StrongARMPPCInfo), + VMSTATE_UINT32(ppfr, StrongARMPPCInfo), + VMSTATE_UINT32(prev_level, StrongARMPPCInfo), + VMSTATE_END_OF_LIST(), + }, +}; + +static void strongarm_ppc_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = strongarm_ppc_init; + dc->desc = "StrongARM PPC controller"; + dc->vmsd = &vmstate_strongarm_ppc_regs; +} + +static const TypeInfo strongarm_ppc_info = { + .name = TYPE_STRONGARM_PPC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(StrongARMPPCInfo), + .class_init = strongarm_ppc_class_init, +}; + +/* UART Ports */ +#define UTCR0 0x00 +#define UTCR1 0x04 +#define UTCR2 0x08 +#define UTCR3 0x0c +#define UTDR 0x14 +#define UTSR0 0x1c +#define UTSR1 0x20 + +#define UTCR0_PE (1 << 0) /* Parity enable */ +#define UTCR0_OES (1 << 1) /* Even parity */ +#define UTCR0_SBS (1 << 2) /* 2 stop bits */ +#define UTCR0_DSS (1 << 3) /* 8-bit data */ + +#define UTCR3_RXE (1 << 0) /* Rx enable */ +#define UTCR3_TXE (1 << 1) /* Tx enable */ +#define UTCR3_BRK (1 << 2) /* Force Break */ +#define UTCR3_RIE (1 << 3) /* Rx int enable */ +#define UTCR3_TIE (1 << 4) /* Tx int enable */ +#define UTCR3_LBM (1 << 5) /* Loopback */ + +#define UTSR0_TFS (1 << 0) /* Tx FIFO nearly empty */ +#define UTSR0_RFS (1 << 1) /* Rx FIFO nearly full */ +#define UTSR0_RID (1 << 2) /* Receiver Idle */ +#define UTSR0_RBB (1 << 3) /* Receiver begin break */ +#define UTSR0_REB (1 << 4) /* Receiver end break */ +#define UTSR0_EIF (1 << 5) /* Error in FIFO */ + +#define UTSR1_RNE (1 << 1) /* Receive FIFO not empty */ +#define UTSR1_TNF (1 << 2) /* Transmit FIFO not full */ +#define UTSR1_PRE (1 << 3) /* Parity error */ +#define UTSR1_FRE (1 << 4) /* Frame error */ +#define UTSR1_ROR (1 << 5) /* Receive Over Run */ + +#define RX_FIFO_PRE (1 << 8) +#define RX_FIFO_FRE (1 << 9) +#define RX_FIFO_ROR (1 << 10) + +#define TYPE_STRONGARM_UART "strongarm-uart" +#define STRONGARM_UART(obj) \ + OBJECT_CHECK(StrongARMUARTState, (obj), TYPE_STRONGARM_UART) + +typedef struct StrongARMUARTState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + CharDriverState *chr; + qemu_irq irq; + + uint8_t utcr0; + uint16_t brd; + uint8_t utcr3; + uint8_t utsr0; + uint8_t utsr1; + + uint8_t tx_fifo[8]; + uint8_t tx_start; + uint8_t tx_len; + uint16_t rx_fifo[12]; /* value + error flags in high bits */ + uint8_t rx_start; + uint8_t rx_len; + + uint64_t char_transmit_time; /* time to transmit a char in ticks*/ + bool wait_break_end; + QEMUTimer *rx_timeout_timer; + QEMUTimer *tx_timer; +} StrongARMUARTState; + +static void strongarm_uart_update_status(StrongARMUARTState *s) +{ + uint16_t utsr1 = 0; + + if (s->tx_len != 8) { + utsr1 |= UTSR1_TNF; + } + + if (s->rx_len != 0) { + uint16_t ent = s->rx_fifo[s->rx_start]; + + utsr1 |= UTSR1_RNE; + if (ent & RX_FIFO_PRE) { + s->utsr1 |= UTSR1_PRE; + } + if (ent & RX_FIFO_FRE) { + s->utsr1 |= UTSR1_FRE; + } + if (ent & RX_FIFO_ROR) { + s->utsr1 |= UTSR1_ROR; + } + } + + s->utsr1 = utsr1; +} + +static void strongarm_uart_update_int_status(StrongARMUARTState *s) +{ + uint16_t utsr0 = s->utsr0 & + (UTSR0_REB | UTSR0_RBB | UTSR0_RID); + int i; + + if ((s->utcr3 & UTCR3_TXE) && + (s->utcr3 & UTCR3_TIE) && + s->tx_len <= 4) { + utsr0 |= UTSR0_TFS; + } + + if ((s->utcr3 & UTCR3_RXE) && + (s->utcr3 & UTCR3_RIE) && + s->rx_len > 4) { + utsr0 |= UTSR0_RFS; + } + + for (i = 0; i < s->rx_len && i < 4; i++) + if (s->rx_fifo[(s->rx_start + i) % 12] & ~0xff) { + utsr0 |= UTSR0_EIF; + break; + } + + s->utsr0 = utsr0; + qemu_set_irq(s->irq, utsr0); +} + +static void strongarm_uart_update_parameters(StrongARMUARTState *s) +{ + int speed, parity, data_bits, stop_bits, frame_size; + QEMUSerialSetParams ssp; + + /* Start bit. */ + frame_size = 1; + if (s->utcr0 & UTCR0_PE) { + /* Parity bit. */ + frame_size++; + if (s->utcr0 & UTCR0_OES) { + parity = 'E'; + } else { + parity = 'O'; + } + } else { + parity = 'N'; + } + if (s->utcr0 & UTCR0_SBS) { + stop_bits = 2; + } else { + stop_bits = 1; + } + + data_bits = (s->utcr0 & UTCR0_DSS) ? 8 : 7; + frame_size += data_bits + stop_bits; + speed = 3686400 / 16 / (s->brd + 1); + ssp.speed = speed; + ssp.parity = parity; + ssp.data_bits = data_bits; + ssp.stop_bits = stop_bits; + s->char_transmit_time = (get_ticks_per_sec() / speed) * frame_size; + if (s->chr) { + qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp); + } + + DPRINTF(stderr, "%s speed=%d parity=%c data=%d stop=%d\n", s->chr->label, + speed, parity, data_bits, stop_bits); +} + +static void strongarm_uart_rx_to(void *opaque) +{ + StrongARMUARTState *s = opaque; + + if (s->rx_len) { + s->utsr0 |= UTSR0_RID; + strongarm_uart_update_int_status(s); + } +} + +static void strongarm_uart_rx_push(StrongARMUARTState *s, uint16_t c) +{ + if ((s->utcr3 & UTCR3_RXE) == 0) { + /* rx disabled */ + return; + } + + if (s->wait_break_end) { + s->utsr0 |= UTSR0_REB; + s->wait_break_end = false; + } + + if (s->rx_len < 12) { + s->rx_fifo[(s->rx_start + s->rx_len) % 12] = c; + s->rx_len++; + } else + s->rx_fifo[(s->rx_start + 11) % 12] |= RX_FIFO_ROR; +} + +static int strongarm_uart_can_receive(void *opaque) +{ + StrongARMUARTState *s = opaque; + + if (s->rx_len == 12) { + return 0; + } + /* It's best not to get more than 2/3 of RX FIFO, so advertise that much */ + if (s->rx_len < 8) { + return 8 - s->rx_len; + } + return 1; +} + +static void strongarm_uart_receive(void *opaque, const uint8_t *buf, int size) +{ + StrongARMUARTState *s = opaque; + int i; + + for (i = 0; i < size; i++) { + strongarm_uart_rx_push(s, buf[i]); + } + + /* call the timeout receive callback in 3 char transmit time */ + timer_mod(s->rx_timeout_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->char_transmit_time * 3); + + strongarm_uart_update_status(s); + strongarm_uart_update_int_status(s); +} + +static void strongarm_uart_event(void *opaque, int event) +{ + StrongARMUARTState *s = opaque; + if (event == CHR_EVENT_BREAK) { + s->utsr0 |= UTSR0_RBB; + strongarm_uart_rx_push(s, RX_FIFO_FRE); + s->wait_break_end = true; + strongarm_uart_update_status(s); + strongarm_uart_update_int_status(s); + } +} + +static void strongarm_uart_tx(void *opaque) +{ + StrongARMUARTState *s = opaque; + uint64_t new_xmit_ts = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + + if (s->utcr3 & UTCR3_LBM) /* loopback */ { + strongarm_uart_receive(s, &s->tx_fifo[s->tx_start], 1); + } else if (s->chr) { + qemu_chr_fe_write(s->chr, &s->tx_fifo[s->tx_start], 1); + } + + s->tx_start = (s->tx_start + 1) % 8; + s->tx_len--; + if (s->tx_len) { + timer_mod(s->tx_timer, new_xmit_ts + s->char_transmit_time); + } + strongarm_uart_update_status(s); + strongarm_uart_update_int_status(s); +} + +static uint64_t strongarm_uart_read(void *opaque, hwaddr addr, + unsigned size) +{ + StrongARMUARTState *s = opaque; + uint16_t ret; + + switch (addr) { + case UTCR0: + return s->utcr0; + + case UTCR1: + return s->brd >> 8; + + case UTCR2: + return s->brd & 0xff; + + case UTCR3: + return s->utcr3; + + case UTDR: + if (s->rx_len != 0) { + ret = s->rx_fifo[s->rx_start]; + s->rx_start = (s->rx_start + 1) % 12; + s->rx_len--; + strongarm_uart_update_status(s); + strongarm_uart_update_int_status(s); + return ret; + } + return 0; + + case UTSR0: + return s->utsr0; + + case UTSR1: + return s->utsr1; + + default: + printf("%s: Bad register 0x" TARGET_FMT_plx "\n", __func__, addr); + return 0; + } +} + +static void strongarm_uart_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + StrongARMUARTState *s = opaque; + + switch (addr) { + case UTCR0: + s->utcr0 = value & 0x7f; + strongarm_uart_update_parameters(s); + break; + + case UTCR1: + s->brd = (s->brd & 0xff) | ((value & 0xf) << 8); + strongarm_uart_update_parameters(s); + break; + + case UTCR2: + s->brd = (s->brd & 0xf00) | (value & 0xff); + strongarm_uart_update_parameters(s); + break; + + case UTCR3: + s->utcr3 = value & 0x3f; + if ((s->utcr3 & UTCR3_RXE) == 0) { + s->rx_len = 0; + } + if ((s->utcr3 & UTCR3_TXE) == 0) { + s->tx_len = 0; + } + strongarm_uart_update_status(s); + strongarm_uart_update_int_status(s); + break; + + case UTDR: + if ((s->utcr3 & UTCR3_TXE) && s->tx_len != 8) { + s->tx_fifo[(s->tx_start + s->tx_len) % 8] = value; + s->tx_len++; + strongarm_uart_update_status(s); + strongarm_uart_update_int_status(s); + if (s->tx_len == 1) { + strongarm_uart_tx(s); + } + } + break; + + case UTSR0: + s->utsr0 = s->utsr0 & ~(value & + (UTSR0_REB | UTSR0_RBB | UTSR0_RID)); + strongarm_uart_update_int_status(s); + break; + + default: + printf("%s: Bad register 0x" TARGET_FMT_plx "\n", __func__, addr); + } +} + +static const MemoryRegionOps strongarm_uart_ops = { + .read = strongarm_uart_read, + .write = strongarm_uart_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int strongarm_uart_init(SysBusDevice *dev) +{ + StrongARMUARTState *s = STRONGARM_UART(dev); + + memory_region_init_io(&s->iomem, OBJECT(s), &strongarm_uart_ops, s, + "uart", 0x10000); + sysbus_init_mmio(dev, &s->iomem); + sysbus_init_irq(dev, &s->irq); + + s->rx_timeout_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, strongarm_uart_rx_to, s); + s->tx_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, strongarm_uart_tx, s); + + if (s->chr) { + qemu_chr_add_handlers(s->chr, + strongarm_uart_can_receive, + strongarm_uart_receive, + strongarm_uart_event, + s); + } + + return 0; +} + +static void strongarm_uart_reset(DeviceState *dev) +{ + StrongARMUARTState *s = STRONGARM_UART(dev); + + s->utcr0 = UTCR0_DSS; /* 8 data, no parity */ + s->brd = 23; /* 9600 */ + /* enable send & recv - this actually violates spec */ + s->utcr3 = UTCR3_TXE | UTCR3_RXE; + + s->rx_len = s->tx_len = 0; + + strongarm_uart_update_parameters(s); + strongarm_uart_update_status(s); + strongarm_uart_update_int_status(s); +} + +static int strongarm_uart_post_load(void *opaque, int version_id) +{ + StrongARMUARTState *s = opaque; + + strongarm_uart_update_parameters(s); + strongarm_uart_update_status(s); + strongarm_uart_update_int_status(s); + + /* tx and restart timer */ + if (s->tx_len) { + strongarm_uart_tx(s); + } + + /* restart rx timeout timer */ + if (s->rx_len) { + timer_mod(s->rx_timeout_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->char_transmit_time * 3); + } + + return 0; +} + +static const VMStateDescription vmstate_strongarm_uart_regs = { + .name = "strongarm-uart", + .version_id = 0, + .minimum_version_id = 0, + .post_load = strongarm_uart_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT8(utcr0, StrongARMUARTState), + VMSTATE_UINT16(brd, StrongARMUARTState), + VMSTATE_UINT8(utcr3, StrongARMUARTState), + VMSTATE_UINT8(utsr0, StrongARMUARTState), + VMSTATE_UINT8_ARRAY(tx_fifo, StrongARMUARTState, 8), + VMSTATE_UINT8(tx_start, StrongARMUARTState), + VMSTATE_UINT8(tx_len, StrongARMUARTState), + VMSTATE_UINT16_ARRAY(rx_fifo, StrongARMUARTState, 12), + VMSTATE_UINT8(rx_start, StrongARMUARTState), + VMSTATE_UINT8(rx_len, StrongARMUARTState), + VMSTATE_BOOL(wait_break_end, StrongARMUARTState), + VMSTATE_END_OF_LIST(), + }, +}; + +static Property strongarm_uart_properties[] = { + DEFINE_PROP_CHR("chardev", StrongARMUARTState, chr), + DEFINE_PROP_END_OF_LIST(), +}; + +static void strongarm_uart_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = strongarm_uart_init; + dc->desc = "StrongARM UART controller"; + dc->reset = strongarm_uart_reset; + dc->vmsd = &vmstate_strongarm_uart_regs; + dc->props = strongarm_uart_properties; +} + +static const TypeInfo strongarm_uart_info = { + .name = TYPE_STRONGARM_UART, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(StrongARMUARTState), + .class_init = strongarm_uart_class_init, +}; + +/* Synchronous Serial Ports */ + +#define TYPE_STRONGARM_SSP "strongarm-ssp" +#define STRONGARM_SSP(obj) \ + OBJECT_CHECK(StrongARMSSPState, (obj), TYPE_STRONGARM_SSP) + +typedef struct StrongARMSSPState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + qemu_irq irq; + SSIBus *bus; + + uint16_t sscr[2]; + uint16_t sssr; + + uint16_t rx_fifo[8]; + uint8_t rx_level; + uint8_t rx_start; +} StrongARMSSPState; + +#define SSCR0 0x60 /* SSP Control register 0 */ +#define SSCR1 0x64 /* SSP Control register 1 */ +#define SSDR 0x6c /* SSP Data register */ +#define SSSR 0x74 /* SSP Status register */ + +/* Bitfields for above registers */ +#define SSCR0_SPI(x) (((x) & 0x30) == 0x00) +#define SSCR0_SSP(x) (((x) & 0x30) == 0x10) +#define SSCR0_UWIRE(x) (((x) & 0x30) == 0x20) +#define SSCR0_PSP(x) (((x) & 0x30) == 0x30) +#define SSCR0_SSE (1 << 7) +#define SSCR0_DSS(x) (((x) & 0xf) + 1) +#define SSCR1_RIE (1 << 0) +#define SSCR1_TIE (1 << 1) +#define SSCR1_LBM (1 << 2) +#define SSSR_TNF (1 << 2) +#define SSSR_RNE (1 << 3) +#define SSSR_TFS (1 << 5) +#define SSSR_RFS (1 << 6) +#define SSSR_ROR (1 << 7) +#define SSSR_RW 0x0080 + +static void strongarm_ssp_int_update(StrongARMSSPState *s) +{ + int level = 0; + + level |= (s->sssr & SSSR_ROR); + level |= (s->sssr & SSSR_RFS) && (s->sscr[1] & SSCR1_RIE); + level |= (s->sssr & SSSR_TFS) && (s->sscr[1] & SSCR1_TIE); + qemu_set_irq(s->irq, level); +} + +static void strongarm_ssp_fifo_update(StrongARMSSPState *s) +{ + s->sssr &= ~SSSR_TFS; + s->sssr &= ~SSSR_TNF; + if (s->sscr[0] & SSCR0_SSE) { + if (s->rx_level >= 4) { + s->sssr |= SSSR_RFS; + } else { + s->sssr &= ~SSSR_RFS; + } + if (s->rx_level) { + s->sssr |= SSSR_RNE; + } else { + s->sssr &= ~SSSR_RNE; + } + /* TX FIFO is never filled, so it is always in underrun + condition if SSP is enabled */ + s->sssr |= SSSR_TFS; + s->sssr |= SSSR_TNF; + } + + strongarm_ssp_int_update(s); +} + +static uint64_t strongarm_ssp_read(void *opaque, hwaddr addr, + unsigned size) +{ + StrongARMSSPState *s = opaque; + uint32_t retval; + + switch (addr) { + case SSCR0: + return s->sscr[0]; + case SSCR1: + return s->sscr[1]; + case SSSR: + return s->sssr; + case SSDR: + if (~s->sscr[0] & SSCR0_SSE) { + return 0xffffffff; + } + if (s->rx_level < 1) { + printf("%s: SSP Rx Underrun\n", __func__); + return 0xffffffff; + } + s->rx_level--; + retval = s->rx_fifo[s->rx_start++]; + s->rx_start &= 0x7; + strongarm_ssp_fifo_update(s); + return retval; + default: + printf("%s: Bad register 0x" TARGET_FMT_plx "\n", __func__, addr); + break; + } + return 0; +} + +static void strongarm_ssp_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + StrongARMSSPState *s = opaque; + + switch (addr) { + case SSCR0: + s->sscr[0] = value & 0xffbf; + if ((s->sscr[0] & SSCR0_SSE) && SSCR0_DSS(value) < 4) { + printf("%s: Wrong data size: %i bits\n", __func__, + (int)SSCR0_DSS(value)); + } + if (!(value & SSCR0_SSE)) { + s->sssr = 0; + s->rx_level = 0; + } + strongarm_ssp_fifo_update(s); + break; + + case SSCR1: + s->sscr[1] = value & 0x2f; + if (value & SSCR1_LBM) { + printf("%s: Attempt to use SSP LBM mode\n", __func__); + } + strongarm_ssp_fifo_update(s); + break; + + case SSSR: + s->sssr &= ~(value & SSSR_RW); + strongarm_ssp_int_update(s); + break; + + case SSDR: + if (SSCR0_UWIRE(s->sscr[0])) { + value &= 0xff; + } else + /* Note how 32bits overflow does no harm here */ + value &= (1 << SSCR0_DSS(s->sscr[0])) - 1; + + /* Data goes from here to the Tx FIFO and is shifted out from + * there directly to the slave, no need to buffer it. + */ + if (s->sscr[0] & SSCR0_SSE) { + uint32_t readval; + if (s->sscr[1] & SSCR1_LBM) { + readval = value; + } else { + readval = ssi_transfer(s->bus, value); + } + + if (s->rx_level < 0x08) { + s->rx_fifo[(s->rx_start + s->rx_level++) & 0x7] = readval; + } else { + s->sssr |= SSSR_ROR; + } + } + strongarm_ssp_fifo_update(s); + break; + + default: + printf("%s: Bad register 0x" TARGET_FMT_plx "\n", __func__, addr); + break; + } +} + +static const MemoryRegionOps strongarm_ssp_ops = { + .read = strongarm_ssp_read, + .write = strongarm_ssp_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int strongarm_ssp_post_load(void *opaque, int version_id) +{ + StrongARMSSPState *s = opaque; + + strongarm_ssp_fifo_update(s); + + return 0; +} + +static int strongarm_ssp_init(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + StrongARMSSPState *s = STRONGARM_SSP(dev); + + sysbus_init_irq(sbd, &s->irq); + + memory_region_init_io(&s->iomem, OBJECT(s), &strongarm_ssp_ops, s, + "ssp", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + + s->bus = ssi_create_bus(dev, "ssi"); + return 0; +} + +static void strongarm_ssp_reset(DeviceState *dev) +{ + StrongARMSSPState *s = STRONGARM_SSP(dev); + + s->sssr = 0x03; /* 3 bit data, SPI, disabled */ + s->rx_start = 0; + s->rx_level = 0; +} + +static const VMStateDescription vmstate_strongarm_ssp_regs = { + .name = "strongarm-ssp", + .version_id = 0, + .minimum_version_id = 0, + .post_load = strongarm_ssp_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT16_ARRAY(sscr, StrongARMSSPState, 2), + VMSTATE_UINT16(sssr, StrongARMSSPState), + VMSTATE_UINT16_ARRAY(rx_fifo, StrongARMSSPState, 8), + VMSTATE_UINT8(rx_start, StrongARMSSPState), + VMSTATE_UINT8(rx_level, StrongARMSSPState), + VMSTATE_END_OF_LIST(), + }, +}; + +static void strongarm_ssp_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = strongarm_ssp_init; + dc->desc = "StrongARM SSP controller"; + dc->reset = strongarm_ssp_reset; + dc->vmsd = &vmstate_strongarm_ssp_regs; +} + +static const TypeInfo strongarm_ssp_info = { + .name = TYPE_STRONGARM_SSP, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(StrongARMSSPState), + .class_init = strongarm_ssp_class_init, +}; + +/* Main CPU functions */ +StrongARMState *sa1110_init(MemoryRegion *sysmem, + unsigned int sdram_size, const char *rev) +{ + StrongARMState *s; + int i; + + s = g_malloc0(sizeof(StrongARMState)); + + if (!rev) { + rev = "sa1110-b5"; + } + + if (strncmp(rev, "sa1110", 6)) { + error_report("Machine requires a SA1110 processor."); + exit(1); + } + + s->cpu = cpu_arm_init(rev); + + if (!s->cpu) { + error_report("Unable to find CPU definition"); + exit(1); + } + + memory_region_allocate_system_memory(&s->sdram, NULL, "strongarm.sdram", + sdram_size); + memory_region_add_subregion(sysmem, SA_SDCS0, &s->sdram); + + s->pic = sysbus_create_varargs("strongarm_pic", 0x90050000, + qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_IRQ), + qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_FIQ), + NULL); + + sysbus_create_varargs("pxa25x-timer", 0x90000000, + qdev_get_gpio_in(s->pic, SA_PIC_OSTC0), + qdev_get_gpio_in(s->pic, SA_PIC_OSTC1), + qdev_get_gpio_in(s->pic, SA_PIC_OSTC2), + qdev_get_gpio_in(s->pic, SA_PIC_OSTC3), + NULL); + + sysbus_create_simple(TYPE_STRONGARM_RTC, 0x90010000, + qdev_get_gpio_in(s->pic, SA_PIC_RTC_ALARM)); + + s->gpio = strongarm_gpio_init(0x90040000, s->pic); + + s->ppc = sysbus_create_varargs(TYPE_STRONGARM_PPC, 0x90060000, NULL); + + for (i = 0; sa_serial[i].io_base; i++) { + DeviceState *dev = qdev_create(NULL, TYPE_STRONGARM_UART); + qdev_prop_set_chr(dev, "chardev", serial_hds[i]); + qdev_init_nofail(dev); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, + sa_serial[i].io_base); + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, + qdev_get_gpio_in(s->pic, sa_serial[i].irq)); + } + + s->ssp = sysbus_create_varargs(TYPE_STRONGARM_SSP, 0x80070000, + qdev_get_gpio_in(s->pic, SA_PIC_SSP), NULL); + s->ssp_bus = (SSIBus *)qdev_get_child_bus(s->ssp, "ssi"); + + return s; +} + +static void strongarm_register_types(void) +{ + type_register_static(&strongarm_pic_info); + type_register_static(&strongarm_rtc_sysbus_info); + type_register_static(&strongarm_gpio_info); + type_register_static(&strongarm_ppc_info); + type_register_static(&strongarm_uart_info); + type_register_static(&strongarm_ssp_info); +} + +type_init(strongarm_register_types) diff --git a/qemu/hw/arm/strongarm.h b/qemu/hw/arm/strongarm.h new file mode 100644 index 000000000..2893f9444 --- /dev/null +++ b/qemu/hw/arm/strongarm.h @@ -0,0 +1,68 @@ +#ifndef _STRONGARM_H +#define _STRONGARM_H + +#include "exec/memory.h" + +#define SA_CS0 0x00000000 +#define SA_CS1 0x08000000 +#define SA_CS2 0x10000000 +#define SA_CS3 0x18000000 +#define SA_PCMCIA_CS0 0x20000000 +#define SA_PCMCIA_CS1 0x30000000 +#define SA_CS4 0x40000000 +#define SA_CS5 0x48000000 +/* system registers here */ +#define SA_SDCS0 0xc0000000 +#define SA_SDCS1 0xc8000000 +#define SA_SDCS2 0xd0000000 +#define SA_SDCS3 0xd8000000 + +enum { + SA_PIC_GPIO0_EDGE = 0, + SA_PIC_GPIO1_EDGE, + SA_PIC_GPIO2_EDGE, + SA_PIC_GPIO3_EDGE, + SA_PIC_GPIO4_EDGE, + SA_PIC_GPIO5_EDGE, + SA_PIC_GPIO6_EDGE, + SA_PIC_GPIO7_EDGE, + SA_PIC_GPIO8_EDGE, + SA_PIC_GPIO9_EDGE, + SA_PIC_GPIO10_EDGE, + SA_PIC_GPIOX_EDGE, + SA_PIC_LCD, + SA_PIC_UDC, + SA_PIC_RSVD1, + SA_PIC_UART1, + SA_PIC_UART2, + SA_PIC_UART3, + SA_PIC_MCP, + SA_PIC_SSP, + SA_PIC_DMA_CH0, + SA_PIC_DMA_CH1, + SA_PIC_DMA_CH2, + SA_PIC_DMA_CH3, + SA_PIC_DMA_CH4, + SA_PIC_DMA_CH5, + SA_PIC_OSTC0, + SA_PIC_OSTC1, + SA_PIC_OSTC2, + SA_PIC_OSTC3, + SA_PIC_RTC_HZ, + SA_PIC_RTC_ALARM, +}; + +typedef struct { + ARMCPU *cpu; + MemoryRegion sdram; + DeviceState *pic; + DeviceState *gpio; + DeviceState *ppc; + DeviceState *ssp; + SSIBus *ssp_bus; +} StrongARMState; + +StrongARMState *sa1110_init(MemoryRegion *sysmem, + unsigned int sdram_size, const char *rev); + +#endif diff --git a/qemu/hw/arm/sysbus-fdt.c b/qemu/hw/arm/sysbus-fdt.c new file mode 100644 index 000000000..9d28797c8 --- /dev/null +++ b/qemu/hw/arm/sysbus-fdt.c @@ -0,0 +1,247 @@ +/* + * ARM Platform Bus device tree generation helpers + * + * Copyright (c) 2014 Linaro Limited + * + * Authors: + * Alex Graf <agraf@suse.de> + * Eric Auger <eric.auger@linaro.org> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2 or later, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "hw/arm/sysbus-fdt.h" +#include "qemu/error-report.h" +#include "sysemu/device_tree.h" +#include "hw/platform-bus.h" +#include "sysemu/sysemu.h" +#include "hw/vfio/vfio-platform.h" +#include "hw/vfio/vfio-calxeda-xgmac.h" +#include "hw/arm/fdt.h" + +/* + * internal struct that contains the information to create dynamic + * sysbus device node + */ +typedef struct PlatformBusFDTData { + void *fdt; /* device tree handle */ + int irq_start; /* index of the first IRQ usable by platform bus devices */ + const char *pbus_node_name; /* name of the platform bus node */ + PlatformBusDevice *pbus; +} PlatformBusFDTData; + +/* + * struct used when calling the machine init done notifier + * that constructs the fdt nodes of platform bus devices + */ +typedef struct PlatformBusFDTNotifierParams { + Notifier notifier; + ARMPlatformBusFDTParams *fdt_params; +} PlatformBusFDTNotifierParams; + +/* struct that associates a device type name and a node creation function */ +typedef struct NodeCreationPair { + const char *typename; + int (*add_fdt_node_fn)(SysBusDevice *sbdev, void *opaque); +} NodeCreationPair; + +/* Device Specific Code */ + +/** + * add_calxeda_midway_xgmac_fdt_node + * + * Generates a simple node with following properties: + * compatible string, regs, interrupts, dma-coherent + */ +static int add_calxeda_midway_xgmac_fdt_node(SysBusDevice *sbdev, void *opaque) +{ + PlatformBusFDTData *data = opaque; + PlatformBusDevice *pbus = data->pbus; + void *fdt = data->fdt; + const char *parent_node = data->pbus_node_name; + int compat_str_len, i, ret = -1; + char *nodename; + uint32_t *irq_attr, *reg_attr; + uint64_t mmio_base, irq_number; + VFIOPlatformDevice *vdev = VFIO_PLATFORM_DEVICE(sbdev); + VFIODevice *vbasedev = &vdev->vbasedev; + + mmio_base = platform_bus_get_mmio_addr(pbus, sbdev, 0); + nodename = g_strdup_printf("%s/%s@%" PRIx64, parent_node, + vbasedev->name, mmio_base); + qemu_fdt_add_subnode(fdt, nodename); + + compat_str_len = strlen(vdev->compat) + 1; + qemu_fdt_setprop(fdt, nodename, "compatible", + vdev->compat, compat_str_len); + + qemu_fdt_setprop(fdt, nodename, "dma-coherent", "", 0); + + reg_attr = g_new(uint32_t, vbasedev->num_regions * 2); + for (i = 0; i < vbasedev->num_regions; i++) { + mmio_base = platform_bus_get_mmio_addr(pbus, sbdev, i); + reg_attr[2 * i] = cpu_to_be32(mmio_base); + reg_attr[2 * i + 1] = cpu_to_be32( + memory_region_size(&vdev->regions[i]->mem)); + } + ret = qemu_fdt_setprop(fdt, nodename, "reg", reg_attr, + vbasedev->num_regions * 2 * sizeof(uint32_t)); + if (ret) { + error_report("could not set reg property of node %s", nodename); + goto fail_reg; + } + + irq_attr = g_new(uint32_t, vbasedev->num_irqs * 3); + for (i = 0; i < vbasedev->num_irqs; i++) { + irq_number = platform_bus_get_irqn(pbus, sbdev , i) + + data->irq_start; + irq_attr[3 * i] = cpu_to_be32(GIC_FDT_IRQ_TYPE_SPI); + irq_attr[3 * i + 1] = cpu_to_be32(irq_number); + irq_attr[3 * i + 2] = cpu_to_be32(GIC_FDT_IRQ_FLAGS_LEVEL_HI); + } + ret = qemu_fdt_setprop(fdt, nodename, "interrupts", + irq_attr, vbasedev->num_irqs * 3 * sizeof(uint32_t)); + if (ret) { + error_report("could not set interrupts property of node %s", + nodename); + } + g_free(irq_attr); +fail_reg: + g_free(reg_attr); + g_free(nodename); + return ret; +} + +/* list of supported dynamic sysbus devices */ +static const NodeCreationPair add_fdt_node_functions[] = { + {TYPE_VFIO_CALXEDA_XGMAC, add_calxeda_midway_xgmac_fdt_node}, + {"", NULL}, /* last element */ +}; + +/* Generic Code */ + +/** + * add_fdt_node - add the device tree node of a dynamic sysbus device + * + * @sbdev: handle to the sysbus device + * @opaque: handle to the PlatformBusFDTData + * + * Checks the sysbus type belongs to the list of device types that + * are dynamically instantiable and if so call the node creation + * function. + */ +static int add_fdt_node(SysBusDevice *sbdev, void *opaque) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(add_fdt_node_functions); i++) { + if (!strcmp(object_get_typename(OBJECT(sbdev)), + add_fdt_node_functions[i].typename)) { + ret = add_fdt_node_functions[i].add_fdt_node_fn(sbdev, opaque); + assert(!ret); + return 0; + } + } + error_report("Device %s can not be dynamically instantiated", + qdev_fw_name(DEVICE(sbdev))); + exit(1); +} + +/** + * add_all_platform_bus_fdt_nodes - create all the platform bus nodes + * + * builds the parent platform bus node and all the nodes of dynamic + * sysbus devices attached to it. + */ +static void add_all_platform_bus_fdt_nodes(ARMPlatformBusFDTParams *fdt_params) +{ + const char platcomp[] = "qemu,platform\0simple-bus"; + PlatformBusDevice *pbus; + DeviceState *dev; + gchar *node; + uint64_t addr, size; + int irq_start, dtb_size; + struct arm_boot_info *info = fdt_params->binfo; + const ARMPlatformBusSystemParams *params = fdt_params->system_params; + const char *intc = fdt_params->intc; + void *fdt = info->get_dtb(info, &dtb_size); + + /* + * If the user provided a dtb, we assume the dynamic sysbus nodes + * already are integrated there. This corresponds to a use case where + * the dynamic sysbus nodes are complex and their generation is not yet + * supported. In that case the user can take charge of the guest dt + * while qemu takes charge of the qom stuff. + */ + if (info->dtb_filename) { + return; + } + + assert(fdt); + + node = g_strdup_printf("/platform@%"PRIx64, params->platform_bus_base); + addr = params->platform_bus_base; + size = params->platform_bus_size; + irq_start = params->platform_bus_first_irq; + + /* Create a /platform node that we can put all devices into */ + qemu_fdt_add_subnode(fdt, node); + qemu_fdt_setprop(fdt, node, "compatible", platcomp, sizeof(platcomp)); + + /* Our platform bus region is less than 32bits, so 1 cell is enough for + * address and size + */ + qemu_fdt_setprop_cells(fdt, node, "#size-cells", 1); + qemu_fdt_setprop_cells(fdt, node, "#address-cells", 1); + qemu_fdt_setprop_cells(fdt, node, "ranges", 0, addr >> 32, addr, size); + + qemu_fdt_setprop_phandle(fdt, node, "interrupt-parent", intc); + + dev = qdev_find_recursive(sysbus_get_default(), TYPE_PLATFORM_BUS_DEVICE); + pbus = PLATFORM_BUS_DEVICE(dev); + + /* We can only create dt nodes for dynamic devices when they're ready */ + assert(pbus->done_gathering); + + PlatformBusFDTData data = { + .fdt = fdt, + .irq_start = irq_start, + .pbus_node_name = node, + .pbus = pbus, + }; + + /* Loop through all dynamic sysbus devices and create their node */ + foreach_dynamic_sysbus_device(add_fdt_node, &data); + + g_free(node); +} + +static void platform_bus_fdt_notify(Notifier *notifier, void *data) +{ + PlatformBusFDTNotifierParams *p = DO_UPCAST(PlatformBusFDTNotifierParams, + notifier, notifier); + + add_all_platform_bus_fdt_nodes(p->fdt_params); + g_free(p->fdt_params); + g_free(p); +} + +void arm_register_platform_bus_fdt_creator(ARMPlatformBusFDTParams *fdt_params) +{ + PlatformBusFDTNotifierParams *p = g_new(PlatformBusFDTNotifierParams, 1); + + p->fdt_params = fdt_params; + p->notifier.notify = platform_bus_fdt_notify; + qemu_add_machine_init_done_notifier(&p->notifier); +} diff --git a/qemu/hw/arm/tosa.c b/qemu/hw/arm/tosa.c new file mode 100644 index 000000000..73572ebe0 --- /dev/null +++ b/qemu/hw/arm/tosa.c @@ -0,0 +1,306 @@ +/* vim:set shiftwidth=4 ts=4 et: */ +/* + * PXA255 Sharp Zaurus SL-6000 PDA platform + * + * Copyright (c) 2008 Dmitry Baryshkov + * + * Code based on spitz platform by Andrzej Zaborowski <balrog@zabor.org> + * This code is licensed under the GNU GPL v2. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "hw/hw.h" +#include "hw/arm/pxa.h" +#include "hw/arm/arm.h" +#include "hw/devices.h" +#include "hw/arm/sharpsl.h" +#include "hw/pcmcia.h" +#include "hw/boards.h" +#include "hw/i2c/i2c.h" +#include "hw/ssi.h" +#include "sysemu/block-backend.h" +#include "hw/sysbus.h" +#include "exec/address-spaces.h" + +#define TOSA_RAM 0x04000000 +#define TOSA_ROM 0x00800000 + +#define TOSA_GPIO_USB_IN (5) +#define TOSA_GPIO_nSD_DETECT (9) +#define TOSA_GPIO_ON_RESET (19) +#define TOSA_GPIO_CF_IRQ (21) /* CF slot0 Ready */ +#define TOSA_GPIO_CF_CD (13) +#define TOSA_GPIO_TC6393XB_INT (15) +#define TOSA_GPIO_JC_CF_IRQ (36) /* CF slot1 Ready */ + +#define TOSA_SCOOP_GPIO_BASE 1 +#define TOSA_GPIO_IR_POWERDWN (TOSA_SCOOP_GPIO_BASE + 2) +#define TOSA_GPIO_SD_WP (TOSA_SCOOP_GPIO_BASE + 3) +#define TOSA_GPIO_PWR_ON (TOSA_SCOOP_GPIO_BASE + 4) + +#define TOSA_SCOOP_JC_GPIO_BASE 1 +#define TOSA_GPIO_BT_LED (TOSA_SCOOP_JC_GPIO_BASE + 0) +#define TOSA_GPIO_NOTE_LED (TOSA_SCOOP_JC_GPIO_BASE + 1) +#define TOSA_GPIO_CHRG_ERR_LED (TOSA_SCOOP_JC_GPIO_BASE + 2) +#define TOSA_GPIO_TC6393XB_L3V_ON (TOSA_SCOOP_JC_GPIO_BASE + 5) +#define TOSA_GPIO_WLAN_LED (TOSA_SCOOP_JC_GPIO_BASE + 7) + +#define DAC_BASE 0x4e +#define DAC_CH1 0 +#define DAC_CH2 1 + +static void tosa_microdrive_attach(PXA2xxState *cpu) +{ + PCMCIACardState *md; + DriveInfo *dinfo; + + dinfo = drive_get(IF_IDE, 0, 0); + if (!dinfo || dinfo->media_cd) + return; + md = dscm1xxxx_init(dinfo); + pxa2xx_pcmcia_attach(cpu->pcmcia[0], md); +} + +static void tosa_out_switch(void *opaque, int line, int level) +{ + switch (line) { + case 0: + fprintf(stderr, "blue LED %s.\n", level ? "on" : "off"); + break; + case 1: + fprintf(stderr, "green LED %s.\n", level ? "on" : "off"); + break; + case 2: + fprintf(stderr, "amber LED %s.\n", level ? "on" : "off"); + break; + case 3: + fprintf(stderr, "wlan LED %s.\n", level ? "on" : "off"); + break; + default: + fprintf(stderr, "Uhandled out event: %d = %d\n", line, level); + break; + } +} + + +static void tosa_gpio_setup(PXA2xxState *cpu, + DeviceState *scp0, + DeviceState *scp1, + TC6393xbState *tmio) +{ + qemu_irq *outsignals = qemu_allocate_irqs(tosa_out_switch, cpu, 4); + /* MMC/SD host */ + pxa2xx_mmci_handlers(cpu->mmc, + qdev_get_gpio_in(scp0, TOSA_GPIO_SD_WP), + qemu_irq_invert(qdev_get_gpio_in(cpu->gpio, TOSA_GPIO_nSD_DETECT))); + + /* Handle reset */ + qdev_connect_gpio_out(cpu->gpio, TOSA_GPIO_ON_RESET, cpu->reset); + + /* PCMCIA signals: card's IRQ and Card-Detect */ + pxa2xx_pcmcia_set_irq_cb(cpu->pcmcia[0], + qdev_get_gpio_in(cpu->gpio, TOSA_GPIO_CF_IRQ), + qdev_get_gpio_in(cpu->gpio, TOSA_GPIO_CF_CD)); + + pxa2xx_pcmcia_set_irq_cb(cpu->pcmcia[1], + qdev_get_gpio_in(cpu->gpio, TOSA_GPIO_JC_CF_IRQ), + NULL); + + qdev_connect_gpio_out(scp1, TOSA_GPIO_BT_LED, outsignals[0]); + qdev_connect_gpio_out(scp1, TOSA_GPIO_NOTE_LED, outsignals[1]); + qdev_connect_gpio_out(scp1, TOSA_GPIO_CHRG_ERR_LED, outsignals[2]); + qdev_connect_gpio_out(scp1, TOSA_GPIO_WLAN_LED, outsignals[3]); + + qdev_connect_gpio_out(scp1, TOSA_GPIO_TC6393XB_L3V_ON, tc6393xb_l3v_get(tmio)); + + /* UDC Vbus */ + qemu_irq_raise(qdev_get_gpio_in(cpu->gpio, TOSA_GPIO_USB_IN)); +} + +static uint32_t tosa_ssp_tansfer(SSISlave *dev, uint32_t value) +{ + fprintf(stderr, "TG: %d %02x\n", value >> 5, value & 0x1f); + return 0; +} + +static int tosa_ssp_init(SSISlave *dev) +{ + /* Nothing to do. */ + return 0; +} + +#define TYPE_TOSA_DAC "tosa_dac" +#define TOSA_DAC(obj) OBJECT_CHECK(TosaDACState, (obj), TYPE_TOSA_DAC) + +typedef struct { + I2CSlave parent_obj; + + int len; + char buf[3]; +} TosaDACState; + +static int tosa_dac_send(I2CSlave *i2c, uint8_t data) +{ + TosaDACState *s = TOSA_DAC(i2c); + + s->buf[s->len] = data; + if (s->len ++ > 2) { +#ifdef VERBOSE + fprintf(stderr, "%s: message too long (%i bytes)\n", __FUNCTION__, s->len); +#endif + return 1; + } + + if (s->len == 2) { + fprintf(stderr, "dac: channel %d value 0x%02x\n", + s->buf[0], s->buf[1]); + } + + return 0; +} + +static void tosa_dac_event(I2CSlave *i2c, enum i2c_event event) +{ + TosaDACState *s = TOSA_DAC(i2c); + + s->len = 0; + switch (event) { + case I2C_START_SEND: + break; + case I2C_START_RECV: + printf("%s: recv not supported!!!\n", __FUNCTION__); + break; + case I2C_FINISH: +#ifdef VERBOSE + if (s->len < 2) + printf("%s: message too short (%i bytes)\n", __FUNCTION__, s->len); + if (s->len > 2) + printf("%s: message too long\n", __FUNCTION__); +#endif + break; + default: + break; + } +} + +static int tosa_dac_recv(I2CSlave *s) +{ + printf("%s: recv not supported!!!\n", __FUNCTION__); + return -1; +} + +static int tosa_dac_init(I2CSlave *i2c) +{ + /* Nothing to do. */ + return 0; +} + +static void tosa_tg_init(PXA2xxState *cpu) +{ + I2CBus *bus = pxa2xx_i2c_bus(cpu->i2c[0]); + i2c_create_slave(bus, TYPE_TOSA_DAC, DAC_BASE); + ssi_create_slave(cpu->ssp[1], "tosa-ssp"); +} + + +static struct arm_boot_info tosa_binfo = { + .loader_start = PXA2XX_SDRAM_BASE, + .ram_size = 0x04000000, +}; + +static void tosa_init(MachineState *machine) +{ + const char *cpu_model = machine->cpu_model; + const char *kernel_filename = machine->kernel_filename; + const char *kernel_cmdline = machine->kernel_cmdline; + const char *initrd_filename = machine->initrd_filename; + MemoryRegion *address_space_mem = get_system_memory(); + MemoryRegion *rom = g_new(MemoryRegion, 1); + PXA2xxState *mpu; + TC6393xbState *tmio; + DeviceState *scp0, *scp1; + + if (!cpu_model) + cpu_model = "pxa255"; + + mpu = pxa255_init(address_space_mem, tosa_binfo.ram_size); + + memory_region_init_ram(rom, NULL, "tosa.rom", TOSA_ROM, &error_abort); + vmstate_register_ram_global(rom); + memory_region_set_readonly(rom, true); + memory_region_add_subregion(address_space_mem, 0, rom); + + tmio = tc6393xb_init(address_space_mem, 0x10000000, + qdev_get_gpio_in(mpu->gpio, TOSA_GPIO_TC6393XB_INT)); + + scp0 = sysbus_create_simple("scoop", 0x08800000, NULL); + scp1 = sysbus_create_simple("scoop", 0x14800040, NULL); + + tosa_gpio_setup(mpu, scp0, scp1, tmio); + + tosa_microdrive_attach(mpu); + + tosa_tg_init(mpu); + + tosa_binfo.kernel_filename = kernel_filename; + tosa_binfo.kernel_cmdline = kernel_cmdline; + tosa_binfo.initrd_filename = initrd_filename; + tosa_binfo.board_id = 0x208; + arm_load_kernel(mpu->cpu, &tosa_binfo); + sl_bootparam_write(SL_PXA_PARAM_BASE); +} + +static QEMUMachine tosapda_machine = { + .name = "tosa", + .desc = "Tosa PDA (PXA255)", + .init = tosa_init, +}; + +static void tosapda_machine_init(void) +{ + qemu_register_machine(&tosapda_machine); +} + +machine_init(tosapda_machine_init); + +static void tosa_dac_class_init(ObjectClass *klass, void *data) +{ + I2CSlaveClass *k = I2C_SLAVE_CLASS(klass); + + k->init = tosa_dac_init; + k->event = tosa_dac_event; + k->recv = tosa_dac_recv; + k->send = tosa_dac_send; +} + +static const TypeInfo tosa_dac_info = { + .name = TYPE_TOSA_DAC, + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(TosaDACState), + .class_init = tosa_dac_class_init, +}; + +static void tosa_ssp_class_init(ObjectClass *klass, void *data) +{ + SSISlaveClass *k = SSI_SLAVE_CLASS(klass); + + k->init = tosa_ssp_init; + k->transfer = tosa_ssp_tansfer; +} + +static const TypeInfo tosa_ssp_info = { + .name = "tosa-ssp", + .parent = TYPE_SSI_SLAVE, + .instance_size = sizeof(SSISlave), + .class_init = tosa_ssp_class_init, +}; + +static void tosa_register_types(void) +{ + type_register_static(&tosa_dac_info); + type_register_static(&tosa_ssp_info); +} + +type_init(tosa_register_types) diff --git a/qemu/hw/arm/versatilepb.c b/qemu/hw/arm/versatilepb.c new file mode 100644 index 000000000..6c69f4eaa --- /dev/null +++ b/qemu/hw/arm/versatilepb.c @@ -0,0 +1,437 @@ +/* + * ARM Versatile Platform/Application Baseboard System emulation. + * + * Copyright (c) 2005-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the GPL. + */ + +#include "hw/sysbus.h" +#include "hw/arm/arm.h" +#include "hw/devices.h" +#include "net/net.h" +#include "sysemu/sysemu.h" +#include "hw/pci/pci.h" +#include "hw/i2c/i2c.h" +#include "hw/boards.h" +#include "sysemu/block-backend.h" +#include "exec/address-spaces.h" +#include "hw/block/flash.h" +#include "qemu/error-report.h" + +#define VERSATILE_FLASH_ADDR 0x34000000 +#define VERSATILE_FLASH_SIZE (64 * 1024 * 1024) +#define VERSATILE_FLASH_SECT_SIZE (256 * 1024) + +/* Primary interrupt controller. */ + +#define TYPE_VERSATILE_PB_SIC "versatilepb_sic" +#define VERSATILE_PB_SIC(obj) \ + OBJECT_CHECK(vpb_sic_state, (obj), TYPE_VERSATILE_PB_SIC) + +typedef struct vpb_sic_state { + SysBusDevice parent_obj; + + MemoryRegion iomem; + uint32_t level; + uint32_t mask; + uint32_t pic_enable; + qemu_irq parent[32]; + int irq; +} vpb_sic_state; + +static const VMStateDescription vmstate_vpb_sic = { + .name = "versatilepb_sic", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(level, vpb_sic_state), + VMSTATE_UINT32(mask, vpb_sic_state), + VMSTATE_UINT32(pic_enable, vpb_sic_state), + VMSTATE_END_OF_LIST() + } +}; + +static void vpb_sic_update(vpb_sic_state *s) +{ + uint32_t flags; + + flags = s->level & s->mask; + qemu_set_irq(s->parent[s->irq], flags != 0); +} + +static void vpb_sic_update_pic(vpb_sic_state *s) +{ + int i; + uint32_t mask; + + for (i = 21; i <= 30; i++) { + mask = 1u << i; + if (!(s->pic_enable & mask)) + continue; + qemu_set_irq(s->parent[i], (s->level & mask) != 0); + } +} + +static void vpb_sic_set_irq(void *opaque, int irq, int level) +{ + vpb_sic_state *s = (vpb_sic_state *)opaque; + if (level) + s->level |= 1u << irq; + else + s->level &= ~(1u << irq); + if (s->pic_enable & (1u << irq)) + qemu_set_irq(s->parent[irq], level); + vpb_sic_update(s); +} + +static uint64_t vpb_sic_read(void *opaque, hwaddr offset, + unsigned size) +{ + vpb_sic_state *s = (vpb_sic_state *)opaque; + + switch (offset >> 2) { + case 0: /* STATUS */ + return s->level & s->mask; + case 1: /* RAWSTAT */ + return s->level; + case 2: /* ENABLE */ + return s->mask; + case 4: /* SOFTINT */ + return s->level & 1; + case 8: /* PICENABLE */ + return s->pic_enable; + default: + printf ("vpb_sic_read: Bad register offset 0x%x\n", (int)offset); + return 0; + } +} + +static void vpb_sic_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + vpb_sic_state *s = (vpb_sic_state *)opaque; + + switch (offset >> 2) { + case 2: /* ENSET */ + s->mask |= value; + break; + case 3: /* ENCLR */ + s->mask &= ~value; + break; + case 4: /* SOFTINTSET */ + if (value) + s->mask |= 1; + break; + case 5: /* SOFTINTCLR */ + if (value) + s->mask &= ~1u; + break; + case 8: /* PICENSET */ + s->pic_enable |= (value & 0x7fe00000); + vpb_sic_update_pic(s); + break; + case 9: /* PICENCLR */ + s->pic_enable &= ~value; + vpb_sic_update_pic(s); + break; + default: + printf ("vpb_sic_write: Bad register offset 0x%x\n", (int)offset); + return; + } + vpb_sic_update(s); +} + +static const MemoryRegionOps vpb_sic_ops = { + .read = vpb_sic_read, + .write = vpb_sic_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int vpb_sic_init(SysBusDevice *sbd) +{ + DeviceState *dev = DEVICE(sbd); + vpb_sic_state *s = VERSATILE_PB_SIC(dev); + int i; + + qdev_init_gpio_in(dev, vpb_sic_set_irq, 32); + for (i = 0; i < 32; i++) { + sysbus_init_irq(sbd, &s->parent[i]); + } + s->irq = 31; + memory_region_init_io(&s->iomem, OBJECT(s), &vpb_sic_ops, s, + "vpb-sic", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + return 0; +} + +/* Board init. */ + +/* The AB and PB boards both use the same core, just with different + peripherals and expansion busses. For now we emulate a subset of the + PB peripherals and just change the board ID. */ + +static struct arm_boot_info versatile_binfo; + +static void versatile_init(MachineState *machine, int board_id) +{ + ObjectClass *cpu_oc; + Object *cpuobj; + ARMCPU *cpu; + MemoryRegion *sysmem = get_system_memory(); + MemoryRegion *ram = g_new(MemoryRegion, 1); + qemu_irq pic[32]; + qemu_irq sic[32]; + DeviceState *dev, *sysctl; + SysBusDevice *busdev; + DeviceState *pl041; + PCIBus *pci_bus; + NICInfo *nd; + I2CBus *i2c; + int n; + int done_smc = 0; + DriveInfo *dinfo; + Error *err = NULL; + + if (!machine->cpu_model) { + machine->cpu_model = "arm926"; + } + + cpu_oc = cpu_class_by_name(TYPE_ARM_CPU, machine->cpu_model); + if (!cpu_oc) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + + cpuobj = object_new(object_class_get_name(cpu_oc)); + + /* By default ARM1176 CPUs have EL3 enabled. This board does not + * currently support EL3 so the CPU EL3 property is disabled before + * realization. + */ + if (object_property_find(cpuobj, "has_el3", NULL)) { + object_property_set_bool(cpuobj, false, "has_el3", &err); + if (err) { + error_report_err(err); + exit(1); + } + } + + object_property_set_bool(cpuobj, true, "realized", &err); + if (err) { + error_report_err(err); + exit(1); + } + + cpu = ARM_CPU(cpuobj); + + memory_region_allocate_system_memory(ram, NULL, "versatile.ram", + machine->ram_size); + /* ??? RAM should repeat to fill physical memory space. */ + /* SDRAM at address zero. */ + memory_region_add_subregion(sysmem, 0, ram); + + sysctl = qdev_create(NULL, "realview_sysctl"); + qdev_prop_set_uint32(sysctl, "sys_id", 0x41007004); + qdev_prop_set_uint32(sysctl, "proc_id", 0x02000000); + qdev_init_nofail(sysctl); + sysbus_mmio_map(SYS_BUS_DEVICE(sysctl), 0, 0x10000000); + + dev = sysbus_create_varargs("pl190", 0x10140000, + qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ), + qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_FIQ), + NULL); + for (n = 0; n < 32; n++) { + pic[n] = qdev_get_gpio_in(dev, n); + } + dev = sysbus_create_simple(TYPE_VERSATILE_PB_SIC, 0x10003000, NULL); + for (n = 0; n < 32; n++) { + sysbus_connect_irq(SYS_BUS_DEVICE(dev), n, pic[n]); + sic[n] = qdev_get_gpio_in(dev, n); + } + + sysbus_create_simple("pl050_keyboard", 0x10006000, sic[3]); + sysbus_create_simple("pl050_mouse", 0x10007000, sic[4]); + + dev = qdev_create(NULL, "versatile_pci"); + busdev = SYS_BUS_DEVICE(dev); + qdev_init_nofail(dev); + sysbus_mmio_map(busdev, 0, 0x10001000); /* PCI controller regs */ + sysbus_mmio_map(busdev, 1, 0x41000000); /* PCI self-config */ + sysbus_mmio_map(busdev, 2, 0x42000000); /* PCI config */ + sysbus_mmio_map(busdev, 3, 0x43000000); /* PCI I/O */ + sysbus_mmio_map(busdev, 4, 0x44000000); /* PCI memory window 1 */ + sysbus_mmio_map(busdev, 5, 0x50000000); /* PCI memory window 2 */ + sysbus_mmio_map(busdev, 6, 0x60000000); /* PCI memory window 3 */ + sysbus_connect_irq(busdev, 0, sic[27]); + sysbus_connect_irq(busdev, 1, sic[28]); + sysbus_connect_irq(busdev, 2, sic[29]); + sysbus_connect_irq(busdev, 3, sic[30]); + pci_bus = (PCIBus *)qdev_get_child_bus(dev, "pci"); + + for(n = 0; n < nb_nics; n++) { + nd = &nd_table[n]; + + if (!done_smc && (!nd->model || strcmp(nd->model, "smc91c111") == 0)) { + smc91c111_init(nd, 0x10010000, sic[25]); + done_smc = 1; + } else { + pci_nic_init_nofail(nd, pci_bus, "rtl8139", NULL); + } + } + if (usb_enabled()) { + pci_create_simple(pci_bus, -1, "pci-ohci"); + } + n = drive_get_max_bus(IF_SCSI); + while (n >= 0) { + pci_create_simple(pci_bus, -1, "lsi53c895a"); + n--; + } + + sysbus_create_simple("pl011", 0x101f1000, pic[12]); + sysbus_create_simple("pl011", 0x101f2000, pic[13]); + sysbus_create_simple("pl011", 0x101f3000, pic[14]); + sysbus_create_simple("pl011", 0x10009000, sic[6]); + + sysbus_create_simple("pl080", 0x10130000, pic[17]); + sysbus_create_simple("sp804", 0x101e2000, pic[4]); + sysbus_create_simple("sp804", 0x101e3000, pic[5]); + + sysbus_create_simple("pl061", 0x101e4000, pic[6]); + sysbus_create_simple("pl061", 0x101e5000, pic[7]); + sysbus_create_simple("pl061", 0x101e6000, pic[8]); + sysbus_create_simple("pl061", 0x101e7000, pic[9]); + + /* The versatile/PB actually has a modified Color LCD controller + that includes hardware cursor support from the PL111. */ + dev = sysbus_create_simple("pl110_versatile", 0x10120000, pic[16]); + /* Wire up the mux control signals from the SYS_CLCD register */ + qdev_connect_gpio_out(sysctl, 0, qdev_get_gpio_in(dev, 0)); + + sysbus_create_varargs("pl181", 0x10005000, sic[22], sic[1], NULL); + sysbus_create_varargs("pl181", 0x1000b000, sic[23], sic[2], NULL); + + /* Add PL031 Real Time Clock. */ + sysbus_create_simple("pl031", 0x101e8000, pic[10]); + + dev = sysbus_create_simple("versatile_i2c", 0x10002000, NULL); + i2c = (I2CBus *)qdev_get_child_bus(dev, "i2c"); + i2c_create_slave(i2c, "ds1338", 0x68); + + /* Add PL041 AACI Interface to the LM4549 codec */ + pl041 = qdev_create(NULL, "pl041"); + qdev_prop_set_uint32(pl041, "nc_fifo_depth", 512); + qdev_init_nofail(pl041); + sysbus_mmio_map(SYS_BUS_DEVICE(pl041), 0, 0x10004000); + sysbus_connect_irq(SYS_BUS_DEVICE(pl041), 0, sic[24]); + + /* Memory map for Versatile/PB: */ + /* 0x10000000 System registers. */ + /* 0x10001000 PCI controller config registers. */ + /* 0x10002000 Serial bus interface. */ + /* 0x10003000 Secondary interrupt controller. */ + /* 0x10004000 AACI (audio). */ + /* 0x10005000 MMCI0. */ + /* 0x10006000 KMI0 (keyboard). */ + /* 0x10007000 KMI1 (mouse). */ + /* 0x10008000 Character LCD Interface. */ + /* 0x10009000 UART3. */ + /* 0x1000a000 Smart card 1. */ + /* 0x1000b000 MMCI1. */ + /* 0x10010000 Ethernet. */ + /* 0x10020000 USB. */ + /* 0x10100000 SSMC. */ + /* 0x10110000 MPMC. */ + /* 0x10120000 CLCD Controller. */ + /* 0x10130000 DMA Controller. */ + /* 0x10140000 Vectored interrupt controller. */ + /* 0x101d0000 AHB Monitor Interface. */ + /* 0x101e0000 System Controller. */ + /* 0x101e1000 Watchdog Interface. */ + /* 0x101e2000 Timer 0/1. */ + /* 0x101e3000 Timer 2/3. */ + /* 0x101e4000 GPIO port 0. */ + /* 0x101e5000 GPIO port 1. */ + /* 0x101e6000 GPIO port 2. */ + /* 0x101e7000 GPIO port 3. */ + /* 0x101e8000 RTC. */ + /* 0x101f0000 Smart card 0. */ + /* 0x101f1000 UART0. */ + /* 0x101f2000 UART1. */ + /* 0x101f3000 UART2. */ + /* 0x101f4000 SSPI. */ + /* 0x34000000 NOR Flash */ + + dinfo = drive_get(IF_PFLASH, 0, 0); + if (!pflash_cfi01_register(VERSATILE_FLASH_ADDR, NULL, "versatile.flash", + VERSATILE_FLASH_SIZE, + dinfo ? blk_by_legacy_dinfo(dinfo) : NULL, + VERSATILE_FLASH_SECT_SIZE, + VERSATILE_FLASH_SIZE / VERSATILE_FLASH_SECT_SIZE, + 4, 0x0089, 0x0018, 0x0000, 0x0, 0)) { + fprintf(stderr, "qemu: Error registering flash memory.\n"); + } + + versatile_binfo.ram_size = machine->ram_size; + versatile_binfo.kernel_filename = machine->kernel_filename; + versatile_binfo.kernel_cmdline = machine->kernel_cmdline; + versatile_binfo.initrd_filename = machine->initrd_filename; + versatile_binfo.board_id = board_id; + arm_load_kernel(cpu, &versatile_binfo); +} + +static void vpb_init(MachineState *machine) +{ + versatile_init(machine, 0x183); +} + +static void vab_init(MachineState *machine) +{ + versatile_init(machine, 0x25e); +} + +static QEMUMachine versatilepb_machine = { + .name = "versatilepb", + .desc = "ARM Versatile/PB (ARM926EJ-S)", + .init = vpb_init, + .block_default_type = IF_SCSI, +}; + +static QEMUMachine versatileab_machine = { + .name = "versatileab", + .desc = "ARM Versatile/AB (ARM926EJ-S)", + .init = vab_init, + .block_default_type = IF_SCSI, +}; + +static void versatile_machine_init(void) +{ + qemu_register_machine(&versatilepb_machine); + qemu_register_machine(&versatileab_machine); +} + +machine_init(versatile_machine_init); + +static void vpb_sic_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = vpb_sic_init; + dc->vmsd = &vmstate_vpb_sic; +} + +static const TypeInfo vpb_sic_info = { + .name = TYPE_VERSATILE_PB_SIC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(vpb_sic_state), + .class_init = vpb_sic_class_init, +}; + +static void versatilepb_register_types(void) +{ + type_register_static(&vpb_sic_info); +} + +type_init(versatilepb_register_types) diff --git a/qemu/hw/arm/vexpress.c b/qemu/hw/arm/vexpress.c new file mode 100644 index 000000000..da217884e --- /dev/null +++ b/qemu/hw/arm/vexpress.c @@ -0,0 +1,808 @@ +/* + * ARM Versatile Express emulation. + * + * Copyright (c) 2010 - 2011 B Labs Ltd. + * Copyright (c) 2011 Linaro Limited + * Written by Bahadir Balban, Amit Mahajan, Peter Maydell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "hw/sysbus.h" +#include "hw/arm/arm.h" +#include "hw/arm/primecell.h" +#include "hw/devices.h" +#include "net/net.h" +#include "sysemu/sysemu.h" +#include "hw/boards.h" +#include "hw/loader.h" +#include "exec/address-spaces.h" +#include "sysemu/block-backend.h" +#include "hw/block/flash.h" +#include "sysemu/device_tree.h" +#include "qemu/error-report.h" +#include <libfdt.h> + +#define VEXPRESS_BOARD_ID 0x8e0 +#define VEXPRESS_FLASH_SIZE (64 * 1024 * 1024) +#define VEXPRESS_FLASH_SECT_SIZE (256 * 1024) + +/* Number of virtio transports to create (0..8; limited by + * number of available IRQ lines). + */ +#define NUM_VIRTIO_TRANSPORTS 4 + +/* Address maps for peripherals: + * the Versatile Express motherboard has two possible maps, + * the "legacy" one (used for A9) and the "Cortex-A Series" + * map (used for newer cores). + * Individual daughterboards can also have different maps for + * their peripherals. + */ + +enum { + VE_SYSREGS, + VE_SP810, + VE_SERIALPCI, + VE_PL041, + VE_MMCI, + VE_KMI0, + VE_KMI1, + VE_UART0, + VE_UART1, + VE_UART2, + VE_UART3, + VE_WDT, + VE_TIMER01, + VE_TIMER23, + VE_SERIALDVI, + VE_RTC, + VE_COMPACTFLASH, + VE_CLCD, + VE_NORFLASH0, + VE_NORFLASH1, + VE_NORFLASHALIAS, + VE_SRAM, + VE_VIDEORAM, + VE_ETHERNET, + VE_USB, + VE_DAPROM, + VE_VIRTIO, +}; + +static hwaddr motherboard_legacy_map[] = { + [VE_NORFLASHALIAS] = 0, + /* CS7: 0x10000000 .. 0x10020000 */ + [VE_SYSREGS] = 0x10000000, + [VE_SP810] = 0x10001000, + [VE_SERIALPCI] = 0x10002000, + [VE_PL041] = 0x10004000, + [VE_MMCI] = 0x10005000, + [VE_KMI0] = 0x10006000, + [VE_KMI1] = 0x10007000, + [VE_UART0] = 0x10009000, + [VE_UART1] = 0x1000a000, + [VE_UART2] = 0x1000b000, + [VE_UART3] = 0x1000c000, + [VE_WDT] = 0x1000f000, + [VE_TIMER01] = 0x10011000, + [VE_TIMER23] = 0x10012000, + [VE_VIRTIO] = 0x10013000, + [VE_SERIALDVI] = 0x10016000, + [VE_RTC] = 0x10017000, + [VE_COMPACTFLASH] = 0x1001a000, + [VE_CLCD] = 0x1001f000, + /* CS0: 0x40000000 .. 0x44000000 */ + [VE_NORFLASH0] = 0x40000000, + /* CS1: 0x44000000 .. 0x48000000 */ + [VE_NORFLASH1] = 0x44000000, + /* CS2: 0x48000000 .. 0x4a000000 */ + [VE_SRAM] = 0x48000000, + /* CS3: 0x4c000000 .. 0x50000000 */ + [VE_VIDEORAM] = 0x4c000000, + [VE_ETHERNET] = 0x4e000000, + [VE_USB] = 0x4f000000, +}; + +static hwaddr motherboard_aseries_map[] = { + [VE_NORFLASHALIAS] = 0, + /* CS0: 0x08000000 .. 0x0c000000 */ + [VE_NORFLASH0] = 0x08000000, + /* CS4: 0x0c000000 .. 0x10000000 */ + [VE_NORFLASH1] = 0x0c000000, + /* CS5: 0x10000000 .. 0x14000000 */ + /* CS1: 0x14000000 .. 0x18000000 */ + [VE_SRAM] = 0x14000000, + /* CS2: 0x18000000 .. 0x1c000000 */ + [VE_VIDEORAM] = 0x18000000, + [VE_ETHERNET] = 0x1a000000, + [VE_USB] = 0x1b000000, + /* CS3: 0x1c000000 .. 0x20000000 */ + [VE_DAPROM] = 0x1c000000, + [VE_SYSREGS] = 0x1c010000, + [VE_SP810] = 0x1c020000, + [VE_SERIALPCI] = 0x1c030000, + [VE_PL041] = 0x1c040000, + [VE_MMCI] = 0x1c050000, + [VE_KMI0] = 0x1c060000, + [VE_KMI1] = 0x1c070000, + [VE_UART0] = 0x1c090000, + [VE_UART1] = 0x1c0a0000, + [VE_UART2] = 0x1c0b0000, + [VE_UART3] = 0x1c0c0000, + [VE_WDT] = 0x1c0f0000, + [VE_TIMER01] = 0x1c110000, + [VE_TIMER23] = 0x1c120000, + [VE_VIRTIO] = 0x1c130000, + [VE_SERIALDVI] = 0x1c160000, + [VE_RTC] = 0x1c170000, + [VE_COMPACTFLASH] = 0x1c1a0000, + [VE_CLCD] = 0x1c1f0000, +}; + +/* Structure defining the peculiarities of a specific daughterboard */ + +typedef struct VEDBoardInfo VEDBoardInfo; + +typedef struct { + MachineClass parent; + VEDBoardInfo *daughterboard; +} VexpressMachineClass; + +typedef struct { + MachineState parent; + bool secure; +} VexpressMachineState; + +#define TYPE_VEXPRESS_MACHINE "vexpress" +#define TYPE_VEXPRESS_A9_MACHINE "vexpress-a9" +#define TYPE_VEXPRESS_A15_MACHINE "vexpress-a15" +#define VEXPRESS_MACHINE(obj) \ + OBJECT_CHECK(VexpressMachineState, (obj), TYPE_VEXPRESS_MACHINE) +#define VEXPRESS_MACHINE_GET_CLASS(obj) \ + OBJECT_GET_CLASS(VexpressMachineClass, obj, TYPE_VEXPRESS_MACHINE) +#define VEXPRESS_MACHINE_CLASS(klass) \ + OBJECT_CLASS_CHECK(VexpressMachineClass, klass, TYPE_VEXPRESS_MACHINE) + +typedef void DBoardInitFn(const VexpressMachineState *machine, + ram_addr_t ram_size, + const char *cpu_model, + qemu_irq *pic); + +struct VEDBoardInfo { + struct arm_boot_info bootinfo; + const hwaddr *motherboard_map; + hwaddr loader_start; + const hwaddr gic_cpu_if_addr; + uint32_t proc_id; + uint32_t num_voltage_sensors; + const uint32_t *voltages; + uint32_t num_clocks; + const uint32_t *clocks; + DBoardInitFn *init; +}; + +static void init_cpus(const char *cpu_model, const char *privdev, + hwaddr periphbase, qemu_irq *pic, bool secure) +{ + ObjectClass *cpu_oc = cpu_class_by_name(TYPE_ARM_CPU, cpu_model); + DeviceState *dev; + SysBusDevice *busdev; + int n; + + if (!cpu_oc) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + + /* Create the actual CPUs */ + for (n = 0; n < smp_cpus; n++) { + Object *cpuobj = object_new(object_class_get_name(cpu_oc)); + Error *err = NULL; + + if (!secure) { + object_property_set_bool(cpuobj, false, "has_el3", NULL); + } + + if (object_property_find(cpuobj, "reset-cbar", NULL)) { + object_property_set_int(cpuobj, periphbase, + "reset-cbar", &error_abort); + } + object_property_set_bool(cpuobj, true, "realized", &err); + if (err) { + error_report_err(err); + exit(1); + } + } + + /* Create the private peripheral devices (including the GIC); + * this must happen after the CPUs are created because a15mpcore_priv + * wires itself up to the CPU's generic_timer gpio out lines. + */ + dev = qdev_create(NULL, privdev); + qdev_prop_set_uint32(dev, "num-cpu", smp_cpus); + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(busdev, 0, periphbase); + + /* Interrupts [42:0] are from the motherboard; + * [47:43] are reserved; [63:48] are daughterboard + * peripherals. Note that some documentation numbers + * external interrupts starting from 32 (because there + * are internal interrupts 0..31). + */ + for (n = 0; n < 64; n++) { + pic[n] = qdev_get_gpio_in(dev, n); + } + + /* Connect the CPUs to the GIC */ + for (n = 0; n < smp_cpus; n++) { + DeviceState *cpudev = DEVICE(qemu_get_cpu(n)); + + sysbus_connect_irq(busdev, n, qdev_get_gpio_in(cpudev, ARM_CPU_IRQ)); + sysbus_connect_irq(busdev, n + smp_cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_FIQ)); + } +} + +static void a9_daughterboard_init(const VexpressMachineState *vms, + ram_addr_t ram_size, + const char *cpu_model, + qemu_irq *pic) +{ + MemoryRegion *sysmem = get_system_memory(); + MemoryRegion *ram = g_new(MemoryRegion, 1); + MemoryRegion *lowram = g_new(MemoryRegion, 1); + ram_addr_t low_ram_size; + + if (!cpu_model) { + cpu_model = "cortex-a9"; + } + + if (ram_size > 0x40000000) { + /* 1GB is the maximum the address space permits */ + fprintf(stderr, "vexpress-a9: cannot model more than 1GB RAM\n"); + exit(1); + } + + memory_region_allocate_system_memory(ram, NULL, "vexpress.highmem", + ram_size); + low_ram_size = ram_size; + if (low_ram_size > 0x4000000) { + low_ram_size = 0x4000000; + } + /* RAM is from 0x60000000 upwards. The bottom 64MB of the + * address space should in theory be remappable to various + * things including ROM or RAM; we always map the RAM there. + */ + memory_region_init_alias(lowram, NULL, "vexpress.lowmem", ram, 0, low_ram_size); + memory_region_add_subregion(sysmem, 0x0, lowram); + memory_region_add_subregion(sysmem, 0x60000000, ram); + + /* 0x1e000000 A9MPCore (SCU) private memory region */ + init_cpus(cpu_model, "a9mpcore_priv", 0x1e000000, pic, vms->secure); + + /* Daughterboard peripherals : 0x10020000 .. 0x20000000 */ + + /* 0x10020000 PL111 CLCD (daughterboard) */ + sysbus_create_simple("pl111", 0x10020000, pic[44]); + + /* 0x10060000 AXI RAM */ + /* 0x100e0000 PL341 Dynamic Memory Controller */ + /* 0x100e1000 PL354 Static Memory Controller */ + /* 0x100e2000 System Configuration Controller */ + + sysbus_create_simple("sp804", 0x100e4000, pic[48]); + /* 0x100e5000 SP805 Watchdog module */ + /* 0x100e6000 BP147 TrustZone Protection Controller */ + /* 0x100e9000 PL301 'Fast' AXI matrix */ + /* 0x100ea000 PL301 'Slow' AXI matrix */ + /* 0x100ec000 TrustZone Address Space Controller */ + /* 0x10200000 CoreSight debug APB */ + /* 0x1e00a000 PL310 L2 Cache Controller */ + sysbus_create_varargs("l2x0", 0x1e00a000, NULL); +} + +/* Voltage values for SYS_CFG_VOLT daughterboard registers; + * values are in microvolts. + */ +static const uint32_t a9_voltages[] = { + 1000000, /* VD10 : 1.0V : SoC internal logic voltage */ + 1000000, /* VD10_S2 : 1.0V : PL310, L2 cache, RAM, non-PL310 logic */ + 1000000, /* VD10_S3 : 1.0V : Cortex-A9, cores, MPEs, SCU, PL310 logic */ + 1800000, /* VCC1V8 : 1.8V : DDR2 SDRAM, test chip DDR2 I/O supply */ + 900000, /* DDR2VTT : 0.9V : DDR2 SDRAM VTT termination voltage */ + 3300000, /* VCC3V3 : 3.3V : local board supply for misc external logic */ +}; + +/* Reset values for daughterboard oscillators (in Hz) */ +static const uint32_t a9_clocks[] = { + 45000000, /* AMBA AXI ACLK: 45MHz */ + 23750000, /* daughterboard CLCD clock: 23.75MHz */ + 66670000, /* Test chip reference clock: 66.67MHz */ +}; + +static VEDBoardInfo a9_daughterboard = { + .motherboard_map = motherboard_legacy_map, + .loader_start = 0x60000000, + .gic_cpu_if_addr = 0x1e000100, + .proc_id = 0x0c000191, + .num_voltage_sensors = ARRAY_SIZE(a9_voltages), + .voltages = a9_voltages, + .num_clocks = ARRAY_SIZE(a9_clocks), + .clocks = a9_clocks, + .init = a9_daughterboard_init, +}; + +static void a15_daughterboard_init(const VexpressMachineState *vms, + ram_addr_t ram_size, + const char *cpu_model, + qemu_irq *pic) +{ + MemoryRegion *sysmem = get_system_memory(); + MemoryRegion *ram = g_new(MemoryRegion, 1); + MemoryRegion *sram = g_new(MemoryRegion, 1); + + if (!cpu_model) { + cpu_model = "cortex-a15"; + } + + { + /* We have to use a separate 64 bit variable here to avoid the gcc + * "comparison is always false due to limited range of data type" + * warning if we are on a host where ram_addr_t is 32 bits. + */ + uint64_t rsz = ram_size; + if (rsz > (30ULL * 1024 * 1024 * 1024)) { + fprintf(stderr, "vexpress-a15: cannot model more than 30GB RAM\n"); + exit(1); + } + } + + memory_region_allocate_system_memory(ram, NULL, "vexpress.highmem", + ram_size); + /* RAM is from 0x80000000 upwards; there is no low-memory alias for it. */ + memory_region_add_subregion(sysmem, 0x80000000, ram); + + /* 0x2c000000 A15MPCore private memory region (GIC) */ + init_cpus(cpu_model, "a15mpcore_priv", 0x2c000000, pic, vms->secure); + + /* A15 daughterboard peripherals: */ + + /* 0x20000000: CoreSight interfaces: not modelled */ + /* 0x2a000000: PL301 AXI interconnect: not modelled */ + /* 0x2a420000: SCC: not modelled */ + /* 0x2a430000: system counter: not modelled */ + /* 0x2b000000: HDLCD controller: not modelled */ + /* 0x2b060000: SP805 watchdog: not modelled */ + /* 0x2b0a0000: PL341 dynamic memory controller: not modelled */ + /* 0x2e000000: system SRAM */ + memory_region_init_ram(sram, NULL, "vexpress.a15sram", 0x10000, + &error_abort); + vmstate_register_ram_global(sram); + memory_region_add_subregion(sysmem, 0x2e000000, sram); + + /* 0x7ffb0000: DMA330 DMA controller: not modelled */ + /* 0x7ffd0000: PL354 static memory controller: not modelled */ +} + +static const uint32_t a15_voltages[] = { + 900000, /* Vcore: 0.9V : CPU core voltage */ +}; + +static const uint32_t a15_clocks[] = { + 60000000, /* OSCCLK0: 60MHz : CPU_CLK reference */ + 0, /* OSCCLK1: reserved */ + 0, /* OSCCLK2: reserved */ + 0, /* OSCCLK3: reserved */ + 40000000, /* OSCCLK4: 40MHz : external AXI master clock */ + 23750000, /* OSCCLK5: 23.75MHz : HDLCD PLL reference */ + 50000000, /* OSCCLK6: 50MHz : static memory controller clock */ + 60000000, /* OSCCLK7: 60MHz : SYSCLK reference */ + 40000000, /* OSCCLK8: 40MHz : DDR2 PLL reference */ +}; + +static VEDBoardInfo a15_daughterboard = { + .motherboard_map = motherboard_aseries_map, + .loader_start = 0x80000000, + .gic_cpu_if_addr = 0x2c002000, + .proc_id = 0x14000237, + .num_voltage_sensors = ARRAY_SIZE(a15_voltages), + .voltages = a15_voltages, + .num_clocks = ARRAY_SIZE(a15_clocks), + .clocks = a15_clocks, + .init = a15_daughterboard_init, +}; + +static int add_virtio_mmio_node(void *fdt, uint32_t acells, uint32_t scells, + hwaddr addr, hwaddr size, uint32_t intc, + int irq) +{ + /* Add a virtio_mmio node to the device tree blob: + * virtio_mmio@ADDRESS { + * compatible = "virtio,mmio"; + * reg = <ADDRESS, SIZE>; + * interrupt-parent = <&intc>; + * interrupts = <0, irq, 1>; + * } + * (Note that the format of the interrupts property is dependent on the + * interrupt controller that interrupt-parent points to; these are for + * the ARM GIC and indicate an SPI interrupt, rising-edge-triggered.) + */ + int rc; + char *nodename = g_strdup_printf("/virtio_mmio@%" PRIx64, addr); + + rc = qemu_fdt_add_subnode(fdt, nodename); + rc |= qemu_fdt_setprop_string(fdt, nodename, + "compatible", "virtio,mmio"); + rc |= qemu_fdt_setprop_sized_cells(fdt, nodename, "reg", + acells, addr, scells, size); + qemu_fdt_setprop_cells(fdt, nodename, "interrupt-parent", intc); + qemu_fdt_setprop_cells(fdt, nodename, "interrupts", 0, irq, 1); + g_free(nodename); + if (rc) { + return -1; + } + return 0; +} + +static uint32_t find_int_controller(void *fdt) +{ + /* Find the FDT node corresponding to the interrupt controller + * for virtio-mmio devices. We do this by scanning the fdt for + * a node with the right compatibility, since we know there is + * only one GIC on a vexpress board. + * We return the phandle of the node, or 0 if none was found. + */ + const char *compat = "arm,cortex-a9-gic"; + int offset; + + offset = fdt_node_offset_by_compatible(fdt, -1, compat); + if (offset >= 0) { + return fdt_get_phandle(fdt, offset); + } + return 0; +} + +static void vexpress_modify_dtb(const struct arm_boot_info *info, void *fdt) +{ + uint32_t acells, scells, intc; + const VEDBoardInfo *daughterboard = (const VEDBoardInfo *)info; + + acells = qemu_fdt_getprop_cell(fdt, "/", "#address-cells"); + scells = qemu_fdt_getprop_cell(fdt, "/", "#size-cells"); + intc = find_int_controller(fdt); + if (!intc) { + /* Not fatal, we just won't provide virtio. This will + * happen with older device tree blobs. + */ + fprintf(stderr, "QEMU: warning: couldn't find interrupt controller in " + "dtb; will not include virtio-mmio devices in the dtb.\n"); + } else { + int i; + const hwaddr *map = daughterboard->motherboard_map; + + /* We iterate backwards here because adding nodes + * to the dtb puts them in last-first. + */ + for (i = NUM_VIRTIO_TRANSPORTS - 1; i >= 0; i--) { + add_virtio_mmio_node(fdt, acells, scells, + map[VE_VIRTIO] + 0x200 * i, + 0x200, intc, 40 + i); + } + } +} + + +/* Open code a private version of pflash registration since we + * need to set non-default device width for VExpress platform. + */ +static pflash_t *ve_pflash_cfi01_register(hwaddr base, const char *name, + DriveInfo *di) +{ + DeviceState *dev = qdev_create(NULL, "cfi.pflash01"); + + if (di) { + qdev_prop_set_drive(dev, "drive", blk_by_legacy_dinfo(di), + &error_abort); + } + + qdev_prop_set_uint32(dev, "num-blocks", + VEXPRESS_FLASH_SIZE / VEXPRESS_FLASH_SECT_SIZE); + qdev_prop_set_uint64(dev, "sector-length", VEXPRESS_FLASH_SECT_SIZE); + qdev_prop_set_uint8(dev, "width", 4); + qdev_prop_set_uint8(dev, "device-width", 2); + qdev_prop_set_bit(dev, "big-endian", false); + qdev_prop_set_uint16(dev, "id0", 0x89); + qdev_prop_set_uint16(dev, "id1", 0x18); + qdev_prop_set_uint16(dev, "id2", 0x00); + qdev_prop_set_uint16(dev, "id3", 0x00); + qdev_prop_set_string(dev, "name", name); + qdev_init_nofail(dev); + + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, base); + return OBJECT_CHECK(pflash_t, (dev), "cfi.pflash01"); +} + +static void vexpress_common_init(MachineState *machine) +{ + VexpressMachineState *vms = VEXPRESS_MACHINE(machine); + VexpressMachineClass *vmc = VEXPRESS_MACHINE_GET_CLASS(machine); + VEDBoardInfo *daughterboard = vmc->daughterboard;; + DeviceState *dev, *sysctl, *pl041; + qemu_irq pic[64]; + uint32_t sys_id; + DriveInfo *dinfo; + pflash_t *pflash0; + ram_addr_t vram_size, sram_size; + MemoryRegion *sysmem = get_system_memory(); + MemoryRegion *vram = g_new(MemoryRegion, 1); + MemoryRegion *sram = g_new(MemoryRegion, 1); + MemoryRegion *flashalias = g_new(MemoryRegion, 1); + MemoryRegion *flash0mem; + const hwaddr *map = daughterboard->motherboard_map; + int i; + + daughterboard->init(vms, machine->ram_size, machine->cpu_model, pic); + + /* + * If a bios file was provided, attempt to map it into memory + */ + if (bios_name) { + char *fn; + int image_size; + + if (drive_get(IF_PFLASH, 0, 0)) { + error_report("The contents of the first flash device may be " + "specified with -bios or with -drive if=pflash... " + "but you cannot use both options at once"); + exit(1); + } + fn = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); + if (!fn) { + error_report("Could not find ROM image '%s'", bios_name); + exit(1); + } + image_size = load_image_targphys(fn, map[VE_NORFLASH0], + VEXPRESS_FLASH_SIZE); + g_free(fn); + if (image_size < 0) { + error_report("Could not load ROM image '%s'", bios_name); + exit(1); + } + } + + /* Motherboard peripherals: the wiring is the same but the + * addresses vary between the legacy and A-Series memory maps. + */ + + sys_id = 0x1190f500; + + sysctl = qdev_create(NULL, "realview_sysctl"); + qdev_prop_set_uint32(sysctl, "sys_id", sys_id); + qdev_prop_set_uint32(sysctl, "proc_id", daughterboard->proc_id); + qdev_prop_set_uint32(sysctl, "len-db-voltage", + daughterboard->num_voltage_sensors); + for (i = 0; i < daughterboard->num_voltage_sensors; i++) { + char *propname = g_strdup_printf("db-voltage[%d]", i); + qdev_prop_set_uint32(sysctl, propname, daughterboard->voltages[i]); + g_free(propname); + } + qdev_prop_set_uint32(sysctl, "len-db-clock", + daughterboard->num_clocks); + for (i = 0; i < daughterboard->num_clocks; i++) { + char *propname = g_strdup_printf("db-clock[%d]", i); + qdev_prop_set_uint32(sysctl, propname, daughterboard->clocks[i]); + g_free(propname); + } + qdev_init_nofail(sysctl); + sysbus_mmio_map(SYS_BUS_DEVICE(sysctl), 0, map[VE_SYSREGS]); + + /* VE_SP810: not modelled */ + /* VE_SERIALPCI: not modelled */ + + pl041 = qdev_create(NULL, "pl041"); + qdev_prop_set_uint32(pl041, "nc_fifo_depth", 512); + qdev_init_nofail(pl041); + sysbus_mmio_map(SYS_BUS_DEVICE(pl041), 0, map[VE_PL041]); + sysbus_connect_irq(SYS_BUS_DEVICE(pl041), 0, pic[11]); + + dev = sysbus_create_varargs("pl181", map[VE_MMCI], pic[9], pic[10], NULL); + /* Wire up MMC card detect and read-only signals */ + qdev_connect_gpio_out(dev, 0, + qdev_get_gpio_in(sysctl, ARM_SYSCTL_GPIO_MMC_WPROT)); + qdev_connect_gpio_out(dev, 1, + qdev_get_gpio_in(sysctl, ARM_SYSCTL_GPIO_MMC_CARDIN)); + + sysbus_create_simple("pl050_keyboard", map[VE_KMI0], pic[12]); + sysbus_create_simple("pl050_mouse", map[VE_KMI1], pic[13]); + + sysbus_create_simple("pl011", map[VE_UART0], pic[5]); + sysbus_create_simple("pl011", map[VE_UART1], pic[6]); + sysbus_create_simple("pl011", map[VE_UART2], pic[7]); + sysbus_create_simple("pl011", map[VE_UART3], pic[8]); + + sysbus_create_simple("sp804", map[VE_TIMER01], pic[2]); + sysbus_create_simple("sp804", map[VE_TIMER23], pic[3]); + + /* VE_SERIALDVI: not modelled */ + + sysbus_create_simple("pl031", map[VE_RTC], pic[4]); /* RTC */ + + /* VE_COMPACTFLASH: not modelled */ + + sysbus_create_simple("pl111", map[VE_CLCD], pic[14]); + + dinfo = drive_get_next(IF_PFLASH); + pflash0 = ve_pflash_cfi01_register(map[VE_NORFLASH0], "vexpress.flash0", + dinfo); + if (!pflash0) { + fprintf(stderr, "vexpress: error registering flash 0.\n"); + exit(1); + } + + if (map[VE_NORFLASHALIAS] != -1) { + /* Map flash 0 as an alias into low memory */ + flash0mem = sysbus_mmio_get_region(SYS_BUS_DEVICE(pflash0), 0); + memory_region_init_alias(flashalias, NULL, "vexpress.flashalias", + flash0mem, 0, VEXPRESS_FLASH_SIZE); + memory_region_add_subregion(sysmem, map[VE_NORFLASHALIAS], flashalias); + } + + dinfo = drive_get_next(IF_PFLASH); + if (!ve_pflash_cfi01_register(map[VE_NORFLASH1], "vexpress.flash1", + dinfo)) { + fprintf(stderr, "vexpress: error registering flash 1.\n"); + exit(1); + } + + sram_size = 0x2000000; + memory_region_init_ram(sram, NULL, "vexpress.sram", sram_size, + &error_abort); + vmstate_register_ram_global(sram); + memory_region_add_subregion(sysmem, map[VE_SRAM], sram); + + vram_size = 0x800000; + memory_region_init_ram(vram, NULL, "vexpress.vram", vram_size, + &error_abort); + vmstate_register_ram_global(vram); + memory_region_add_subregion(sysmem, map[VE_VIDEORAM], vram); + + /* 0x4e000000 LAN9118 Ethernet */ + if (nd_table[0].used) { + lan9118_init(&nd_table[0], map[VE_ETHERNET], pic[15]); + } + + /* VE_USB: not modelled */ + + /* VE_DAPROM: not modelled */ + + /* Create mmio transports, so the user can create virtio backends + * (which will be automatically plugged in to the transports). If + * no backend is created the transport will just sit harmlessly idle. + */ + for (i = 0; i < NUM_VIRTIO_TRANSPORTS; i++) { + sysbus_create_simple("virtio-mmio", map[VE_VIRTIO] + 0x200 * i, + pic[40 + i]); + } + + daughterboard->bootinfo.ram_size = machine->ram_size; + daughterboard->bootinfo.kernel_filename = machine->kernel_filename; + daughterboard->bootinfo.kernel_cmdline = machine->kernel_cmdline; + daughterboard->bootinfo.initrd_filename = machine->initrd_filename; + daughterboard->bootinfo.nb_cpus = smp_cpus; + daughterboard->bootinfo.board_id = VEXPRESS_BOARD_ID; + daughterboard->bootinfo.loader_start = daughterboard->loader_start; + daughterboard->bootinfo.smp_loader_start = map[VE_SRAM]; + daughterboard->bootinfo.smp_bootreg_addr = map[VE_SYSREGS] + 0x30; + daughterboard->bootinfo.gic_cpu_if_addr = daughterboard->gic_cpu_if_addr; + daughterboard->bootinfo.modify_dtb = vexpress_modify_dtb; + /* Indicate that when booting Linux we should be in secure state */ + daughterboard->bootinfo.secure_boot = true; + arm_load_kernel(ARM_CPU(first_cpu), &daughterboard->bootinfo); +} + +static bool vexpress_get_secure(Object *obj, Error **errp) +{ + VexpressMachineState *vms = VEXPRESS_MACHINE(obj); + + return vms->secure; +} + +static void vexpress_set_secure(Object *obj, bool value, Error **errp) +{ + VexpressMachineState *vms = VEXPRESS_MACHINE(obj); + + vms->secure = value; +} + +static void vexpress_instance_init(Object *obj) +{ + VexpressMachineState *vms = VEXPRESS_MACHINE(obj); + + /* EL3 is enabled by default on vexpress */ + vms->secure = true; + object_property_add_bool(obj, "secure", vexpress_get_secure, + vexpress_set_secure, NULL); + object_property_set_description(obj, "secure", + "Set on/off to enable/disable the ARM " + "Security Extensions (TrustZone)", + NULL); +} + +static void vexpress_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + + mc->name = TYPE_VEXPRESS_MACHINE; + mc->desc = "ARM Versatile Express"; + mc->init = vexpress_common_init; + mc->block_default_type = IF_SCSI; + mc->max_cpus = 4; +} + +static void vexpress_a9_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + VexpressMachineClass *vmc = VEXPRESS_MACHINE_CLASS(oc); + + mc->name = TYPE_VEXPRESS_A9_MACHINE; + mc->desc = "ARM Versatile Express for Cortex-A9"; + + vmc->daughterboard = &a9_daughterboard;; +} + +static void vexpress_a15_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + VexpressMachineClass *vmc = VEXPRESS_MACHINE_CLASS(oc); + + mc->name = TYPE_VEXPRESS_A15_MACHINE; + mc->desc = "ARM Versatile Express for Cortex-A15"; + + vmc->daughterboard = &a15_daughterboard; +} + +static const TypeInfo vexpress_info = { + .name = TYPE_VEXPRESS_MACHINE, + .parent = TYPE_MACHINE, + .abstract = true, + .instance_size = sizeof(VexpressMachineState), + .instance_init = vexpress_instance_init, + .class_size = sizeof(VexpressMachineClass), + .class_init = vexpress_class_init, +}; + +static const TypeInfo vexpress_a9_info = { + .name = TYPE_VEXPRESS_A9_MACHINE, + .parent = TYPE_VEXPRESS_MACHINE, + .class_init = vexpress_a9_class_init, +}; + +static const TypeInfo vexpress_a15_info = { + .name = TYPE_VEXPRESS_A15_MACHINE, + .parent = TYPE_VEXPRESS_MACHINE, + .class_init = vexpress_a15_class_init, +}; + +static void vexpress_machine_init(void) +{ + type_register_static(&vexpress_info); + type_register_static(&vexpress_a9_info); + type_register_static(&vexpress_a15_info); +} + +machine_init(vexpress_machine_init); diff --git a/qemu/hw/arm/virt-acpi-build.c b/qemu/hw/arm/virt-acpi-build.c new file mode 100644 index 000000000..f36514031 --- /dev/null +++ b/qemu/hw/arm/virt-acpi-build.c @@ -0,0 +1,697 @@ +/* Support for generating ACPI tables and passing them to Guests + * + * ARM virt ACPI generation + * + * Copyright (C) 2008-2010 Kevin O'Connor <kevin@koconnor.net> + * Copyright (C) 2006 Fabrice Bellard + * Copyright (C) 2013 Red Hat Inc + * + * Author: Michael S. Tsirkin <mst@redhat.com> + * + * Copyright (c) 2015 HUAWEI TECHNOLOGIES CO.,LTD. + * + * Author: Shannon Zhao <zhaoshenglong@huawei.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu-common.h" +#include "hw/arm/virt-acpi-build.h" +#include "qemu/bitmap.h" +#include "trace.h" +#include "qom/cpu.h" +#include "target-arm/cpu.h" +#include "hw/acpi/acpi-defs.h" +#include "hw/acpi/acpi.h" +#include "hw/nvram/fw_cfg.h" +#include "hw/acpi/bios-linker-loader.h" +#include "hw/loader.h" +#include "hw/hw.h" +#include "hw/acpi/aml-build.h" +#include "hw/pci/pcie_host.h" +#include "hw/pci/pci.h" + +#define ARM_SPI_BASE 32 + +typedef struct VirtAcpiCpuInfo { + DECLARE_BITMAP(found_cpus, VIRT_ACPI_CPU_ID_LIMIT); +} VirtAcpiCpuInfo; + +static void virt_acpi_get_cpu_info(VirtAcpiCpuInfo *cpuinfo) +{ + CPUState *cpu; + + memset(cpuinfo->found_cpus, 0, sizeof cpuinfo->found_cpus); + CPU_FOREACH(cpu) { + set_bit(cpu->cpu_index, cpuinfo->found_cpus); + } +} + +static void acpi_dsdt_add_cpus(Aml *scope, int smp_cpus) +{ + uint16_t i; + + for (i = 0; i < smp_cpus; i++) { + Aml *dev = aml_device("C%03x", i); + aml_append(dev, aml_name_decl("_HID", aml_string("ACPI0007"))); + aml_append(dev, aml_name_decl("_UID", aml_int(i))); + aml_append(scope, dev); + } +} + +static void acpi_dsdt_add_uart(Aml *scope, const MemMapEntry *uart_memmap, + int uart_irq) +{ + Aml *dev = aml_device("COM0"); + aml_append(dev, aml_name_decl("_HID", aml_string("ARMH0011"))); + aml_append(dev, aml_name_decl("_UID", aml_int(0))); + + Aml *crs = aml_resource_template(); + aml_append(crs, aml_memory32_fixed(uart_memmap->base, + uart_memmap->size, AML_READ_WRITE)); + aml_append(crs, + aml_interrupt(AML_CONSUMER, AML_LEVEL, AML_ACTIVE_HIGH, + AML_EXCLUSIVE, uart_irq)); + aml_append(dev, aml_name_decl("_CRS", crs)); + + /* The _ADR entry is used to link this device to the UART described + * in the SPCR table, i.e. SPCR.base_address.address == _ADR. + */ + aml_append(dev, aml_name_decl("_ADR", aml_int(uart_memmap->base))); + + aml_append(scope, dev); +} + +static void acpi_dsdt_add_rtc(Aml *scope, const MemMapEntry *rtc_memmap, + int rtc_irq) +{ + Aml *dev = aml_device("RTC0"); + aml_append(dev, aml_name_decl("_HID", aml_string("LNRO0013"))); + aml_append(dev, aml_name_decl("_UID", aml_int(0))); + + Aml *crs = aml_resource_template(); + aml_append(crs, aml_memory32_fixed(rtc_memmap->base, + rtc_memmap->size, AML_READ_WRITE)); + aml_append(crs, + aml_interrupt(AML_CONSUMER, AML_LEVEL, AML_ACTIVE_HIGH, + AML_EXCLUSIVE, rtc_irq)); + aml_append(dev, aml_name_decl("_CRS", crs)); + aml_append(scope, dev); +} + +static void acpi_dsdt_add_flash(Aml *scope, const MemMapEntry *flash_memmap) +{ + Aml *dev, *crs; + hwaddr base = flash_memmap->base; + hwaddr size = flash_memmap->size; + + dev = aml_device("FLS0"); + aml_append(dev, aml_name_decl("_HID", aml_string("LNRO0015"))); + aml_append(dev, aml_name_decl("_UID", aml_int(0))); + + crs = aml_resource_template(); + aml_append(crs, aml_memory32_fixed(base, size, AML_READ_WRITE)); + aml_append(dev, aml_name_decl("_CRS", crs)); + aml_append(scope, dev); + + dev = aml_device("FLS1"); + aml_append(dev, aml_name_decl("_HID", aml_string("LNRO0015"))); + aml_append(dev, aml_name_decl("_UID", aml_int(1))); + crs = aml_resource_template(); + aml_append(crs, aml_memory32_fixed(base + size, size, AML_READ_WRITE)); + aml_append(dev, aml_name_decl("_CRS", crs)); + aml_append(scope, dev); +} + +static void acpi_dsdt_add_virtio(Aml *scope, + const MemMapEntry *virtio_mmio_memmap, + int mmio_irq, int num) +{ + hwaddr base = virtio_mmio_memmap->base; + hwaddr size = virtio_mmio_memmap->size; + int irq = mmio_irq; + int i; + + for (i = 0; i < num; i++) { + Aml *dev = aml_device("VR%02u", i); + aml_append(dev, aml_name_decl("_HID", aml_string("LNRO0005"))); + aml_append(dev, aml_name_decl("_UID", aml_int(i))); + + Aml *crs = aml_resource_template(); + aml_append(crs, aml_memory32_fixed(base, size, AML_READ_WRITE)); + aml_append(crs, + aml_interrupt(AML_CONSUMER, AML_LEVEL, AML_ACTIVE_HIGH, + AML_EXCLUSIVE, irq + i)); + aml_append(dev, aml_name_decl("_CRS", crs)); + aml_append(scope, dev); + base += size; + } +} + +static void acpi_dsdt_add_pci(Aml *scope, const MemMapEntry *memmap, int irq) +{ + Aml *method, *crs, *ifctx, *UUID, *ifctx1, *elsectx, *buf; + int i, bus_no; + hwaddr base_mmio = memmap[VIRT_PCIE_MMIO].base; + hwaddr size_mmio = memmap[VIRT_PCIE_MMIO].size; + hwaddr base_pio = memmap[VIRT_PCIE_PIO].base; + hwaddr size_pio = memmap[VIRT_PCIE_PIO].size; + hwaddr base_ecam = memmap[VIRT_PCIE_ECAM].base; + hwaddr size_ecam = memmap[VIRT_PCIE_ECAM].size; + int nr_pcie_buses = size_ecam / PCIE_MMCFG_SIZE_MIN; + + Aml *dev = aml_device("%s", "PCI0"); + aml_append(dev, aml_name_decl("_HID", aml_string("PNP0A08"))); + aml_append(dev, aml_name_decl("_CID", aml_string("PNP0A03"))); + aml_append(dev, aml_name_decl("_SEG", aml_int(0))); + aml_append(dev, aml_name_decl("_BBN", aml_int(0))); + aml_append(dev, aml_name_decl("_ADR", aml_int(0))); + aml_append(dev, aml_name_decl("_UID", aml_string("PCI0"))); + aml_append(dev, aml_name_decl("_STR", aml_unicode("PCIe 0 Device"))); + + /* Declare the PCI Routing Table. */ + Aml *rt_pkg = aml_package(nr_pcie_buses * PCI_NUM_PINS); + for (bus_no = 0; bus_no < nr_pcie_buses; bus_no++) { + for (i = 0; i < PCI_NUM_PINS; i++) { + int gsi = (i + bus_no) % PCI_NUM_PINS; + Aml *pkg = aml_package(4); + aml_append(pkg, aml_int((bus_no << 16) | 0xFFFF)); + aml_append(pkg, aml_int(i)); + aml_append(pkg, aml_name("GSI%d", gsi)); + aml_append(pkg, aml_int(0)); + aml_append(rt_pkg, pkg); + } + } + aml_append(dev, aml_name_decl("_PRT", rt_pkg)); + + /* Create GSI link device */ + for (i = 0; i < PCI_NUM_PINS; i++) { + Aml *dev_gsi = aml_device("GSI%d", i); + aml_append(dev_gsi, aml_name_decl("_HID", aml_string("PNP0C0F"))); + aml_append(dev_gsi, aml_name_decl("_UID", aml_int(0))); + crs = aml_resource_template(); + aml_append(crs, + aml_interrupt(AML_CONSUMER, AML_LEVEL, AML_ACTIVE_HIGH, + AML_EXCLUSIVE, irq + i)); + aml_append(dev_gsi, aml_name_decl("_PRS", crs)); + crs = aml_resource_template(); + aml_append(crs, + aml_interrupt(AML_CONSUMER, AML_LEVEL, AML_ACTIVE_HIGH, + AML_EXCLUSIVE, irq + i)); + aml_append(dev_gsi, aml_name_decl("_CRS", crs)); + method = aml_method("_SRS", 1); + aml_append(dev_gsi, method); + aml_append(dev, dev_gsi); + } + + method = aml_method("_CBA", 0); + aml_append(method, aml_return(aml_int(base_ecam))); + aml_append(dev, method); + + method = aml_method("_CRS", 0); + Aml *rbuf = aml_resource_template(); + aml_append(rbuf, + aml_word_bus_number(AML_MIN_FIXED, AML_MAX_FIXED, AML_POS_DECODE, + 0x0000, 0x0000, nr_pcie_buses - 1, 0x0000, + nr_pcie_buses)); + aml_append(rbuf, + aml_dword_memory(AML_POS_DECODE, AML_MIN_FIXED, AML_MAX_FIXED, + AML_NON_CACHEABLE, AML_READ_WRITE, 0x0000, base_mmio, + base_mmio + size_mmio - 1, 0x0000, size_mmio)); + aml_append(rbuf, + aml_dword_io(AML_MIN_FIXED, AML_MAX_FIXED, AML_POS_DECODE, + AML_ENTIRE_RANGE, 0x0000, 0x0000, size_pio - 1, base_pio, + size_pio)); + + aml_append(method, aml_name_decl("RBUF", rbuf)); + aml_append(method, aml_return(rbuf)); + aml_append(dev, method); + + /* Declare an _OSC (OS Control Handoff) method */ + aml_append(dev, aml_name_decl("SUPP", aml_int(0))); + aml_append(dev, aml_name_decl("CTRL", aml_int(0))); + method = aml_method("_OSC", 4); + aml_append(method, + aml_create_dword_field(aml_arg(3), aml_int(0), "CDW1")); + + /* PCI Firmware Specification 3.0 + * 4.5.1. _OSC Interface for PCI Host Bridge Devices + * The _OSC interface for a PCI/PCI-X/PCI Express hierarchy is + * identified by the Universal Unique IDentifier (UUID) + * 33DB4D5B-1FF7-401C-9657-7441C03DD766 + */ + UUID = aml_touuid("33DB4D5B-1FF7-401C-9657-7441C03DD766"); + ifctx = aml_if(aml_equal(aml_arg(0), UUID)); + aml_append(ifctx, + aml_create_dword_field(aml_arg(3), aml_int(4), "CDW2")); + aml_append(ifctx, + aml_create_dword_field(aml_arg(3), aml_int(8), "CDW3")); + aml_append(ifctx, aml_store(aml_name("CDW2"), aml_name("SUPP"))); + aml_append(ifctx, aml_store(aml_name("CDW3"), aml_name("CTRL"))); + aml_append(ifctx, aml_store(aml_and(aml_name("CTRL"), aml_int(0x1D)), + aml_name("CTRL"))); + + ifctx1 = aml_if(aml_lnot(aml_equal(aml_arg(1), aml_int(0x1)))); + aml_append(ifctx1, aml_store(aml_or(aml_name("CDW1"), aml_int(0x08)), + aml_name("CDW1"))); + aml_append(ifctx, ifctx1); + + ifctx1 = aml_if(aml_lnot(aml_equal(aml_name("CDW3"), aml_name("CTRL")))); + aml_append(ifctx1, aml_store(aml_or(aml_name("CDW1"), aml_int(0x10)), + aml_name("CDW1"))); + aml_append(ifctx, ifctx1); + + aml_append(ifctx, aml_store(aml_name("CTRL"), aml_name("CDW3"))); + aml_append(ifctx, aml_return(aml_arg(3))); + aml_append(method, ifctx); + + elsectx = aml_else(); + aml_append(elsectx, aml_store(aml_or(aml_name("CDW1"), aml_int(4)), + aml_name("CDW1"))); + aml_append(elsectx, aml_return(aml_arg(3))); + aml_append(method, elsectx); + aml_append(dev, method); + + method = aml_method("_DSM", 4); + + /* PCI Firmware Specification 3.0 + * 4.6.1. _DSM for PCI Express Slot Information + * The UUID in _DSM in this context is + * {E5C937D0-3553-4D7A-9117-EA4D19C3434D} + */ + UUID = aml_touuid("E5C937D0-3553-4D7A-9117-EA4D19C3434D"); + ifctx = aml_if(aml_equal(aml_arg(0), UUID)); + ifctx1 = aml_if(aml_equal(aml_arg(2), aml_int(0))); + uint8_t byte_list[1] = {1}; + buf = aml_buffer(1, byte_list); + aml_append(ifctx1, aml_return(buf)); + aml_append(ifctx, ifctx1); + aml_append(method, ifctx); + + byte_list[0] = 0; + buf = aml_buffer(1, byte_list); + aml_append(method, aml_return(buf)); + aml_append(dev, method); + + Aml *dev_rp0 = aml_device("%s", "RP0"); + aml_append(dev_rp0, aml_name_decl("_ADR", aml_int(0))); + aml_append(dev, dev_rp0); + aml_append(scope, dev); +} + +/* RSDP */ +static GArray * +build_rsdp(GArray *rsdp_table, GArray *linker, unsigned rsdt) +{ + AcpiRsdpDescriptor *rsdp = acpi_data_push(rsdp_table, sizeof *rsdp); + + bios_linker_loader_alloc(linker, ACPI_BUILD_RSDP_FILE, 16, + true /* fseg memory */); + + memcpy(&rsdp->signature, "RSD PTR ", sizeof(rsdp->signature)); + memcpy(rsdp->oem_id, ACPI_BUILD_APPNAME6, sizeof(rsdp->oem_id)); + rsdp->length = cpu_to_le32(sizeof(*rsdp)); + rsdp->revision = 0x02; + + /* Point to RSDT */ + rsdp->rsdt_physical_address = cpu_to_le32(rsdt); + /* Address to be filled by Guest linker */ + bios_linker_loader_add_pointer(linker, ACPI_BUILD_RSDP_FILE, + ACPI_BUILD_TABLE_FILE, + rsdp_table, &rsdp->rsdt_physical_address, + sizeof rsdp->rsdt_physical_address); + rsdp->checksum = 0; + /* Checksum to be filled by Guest linker */ + bios_linker_loader_add_checksum(linker, ACPI_BUILD_RSDP_FILE, + rsdp, rsdp, sizeof *rsdp, &rsdp->checksum); + + return rsdp_table; +} + +static void +build_spcr(GArray *table_data, GArray *linker, VirtGuestInfo *guest_info) +{ + AcpiSerialPortConsoleRedirection *spcr; + const MemMapEntry *uart_memmap = &guest_info->memmap[VIRT_UART]; + int irq = guest_info->irqmap[VIRT_UART] + ARM_SPI_BASE; + + spcr = acpi_data_push(table_data, sizeof(*spcr)); + + spcr->interface_type = 0x3; /* ARM PL011 UART */ + + spcr->base_address.space_id = AML_SYSTEM_MEMORY; + spcr->base_address.bit_width = 8; + spcr->base_address.bit_offset = 0; + spcr->base_address.access_width = 1; + spcr->base_address.address = cpu_to_le64(uart_memmap->base); + + spcr->interrupt_types = (1 << 3); /* Bit[3] ARMH GIC interrupt */ + spcr->gsi = cpu_to_le32(irq); /* Global System Interrupt */ + + spcr->baud = 3; /* Baud Rate: 3 = 9600 */ + spcr->parity = 0; /* No Parity */ + spcr->stopbits = 1; /* 1 Stop bit */ + spcr->flowctrl = (1 << 1); /* Bit[1] = RTS/CTS hardware flow control */ + spcr->term_type = 0; /* Terminal Type: 0 = VT100 */ + + spcr->pci_device_id = 0xffff; /* PCI Device ID: not a PCI device */ + spcr->pci_vendor_id = 0xffff; /* PCI Vendor ID: not a PCI device */ + + build_header(linker, table_data, (void *)spcr, "SPCR", sizeof(*spcr), 2); +} + +static void +build_mcfg(GArray *table_data, GArray *linker, VirtGuestInfo *guest_info) +{ + AcpiTableMcfg *mcfg; + const MemMapEntry *memmap = guest_info->memmap; + int len = sizeof(*mcfg) + sizeof(mcfg->allocation[0]); + + mcfg = acpi_data_push(table_data, len); + mcfg->allocation[0].address = cpu_to_le64(memmap[VIRT_PCIE_ECAM].base); + + /* Only a single allocation so no need to play with segments */ + mcfg->allocation[0].pci_segment = cpu_to_le16(0); + mcfg->allocation[0].start_bus_number = 0; + mcfg->allocation[0].end_bus_number = (memmap[VIRT_PCIE_ECAM].size + / PCIE_MMCFG_SIZE_MIN) - 1; + + build_header(linker, table_data, (void *)mcfg, "MCFG", len, 1); +} + +/* GTDT */ +static void +build_gtdt(GArray *table_data, GArray *linker) +{ + int gtdt_start = table_data->len; + AcpiGenericTimerTable *gtdt; + + gtdt = acpi_data_push(table_data, sizeof *gtdt); + /* The interrupt values are the same with the device tree when adding 16 */ + gtdt->secure_el1_interrupt = ARCH_TIMER_S_EL1_IRQ + 16; + gtdt->secure_el1_flags = ACPI_EDGE_SENSITIVE; + + gtdt->non_secure_el1_interrupt = ARCH_TIMER_NS_EL1_IRQ + 16; + gtdt->non_secure_el1_flags = ACPI_EDGE_SENSITIVE; + + gtdt->virtual_timer_interrupt = ARCH_TIMER_VIRT_IRQ + 16; + gtdt->virtual_timer_flags = ACPI_EDGE_SENSITIVE; + + gtdt->non_secure_el2_interrupt = ARCH_TIMER_NS_EL2_IRQ + 16; + gtdt->non_secure_el2_flags = ACPI_EDGE_SENSITIVE; + + build_header(linker, table_data, + (void *)(table_data->data + gtdt_start), "GTDT", + table_data->len - gtdt_start, 2); +} + +/* MADT */ +static void +build_madt(GArray *table_data, GArray *linker, VirtGuestInfo *guest_info, + VirtAcpiCpuInfo *cpuinfo) +{ + int madt_start = table_data->len; + const MemMapEntry *memmap = guest_info->memmap; + const int *irqmap = guest_info->irqmap; + AcpiMultipleApicTable *madt; + AcpiMadtGenericDistributor *gicd; + AcpiMadtGenericMsiFrame *gic_msi; + int i; + + madt = acpi_data_push(table_data, sizeof *madt); + + for (i = 0; i < guest_info->smp_cpus; i++) { + AcpiMadtGenericInterrupt *gicc = acpi_data_push(table_data, + sizeof *gicc); + gicc->type = ACPI_APIC_GENERIC_INTERRUPT; + gicc->length = sizeof(*gicc); + gicc->base_address = memmap[VIRT_GIC_CPU].base; + gicc->cpu_interface_number = i; + gicc->arm_mpidr = i; + gicc->uid = i; + if (test_bit(i, cpuinfo->found_cpus)) { + gicc->flags = cpu_to_le32(ACPI_GICC_ENABLED); + } + } + + gicd = acpi_data_push(table_data, sizeof *gicd); + gicd->type = ACPI_APIC_GENERIC_DISTRIBUTOR; + gicd->length = sizeof(*gicd); + gicd->base_address = memmap[VIRT_GIC_DIST].base; + + gic_msi = acpi_data_push(table_data, sizeof *gic_msi); + gic_msi->type = ACPI_APIC_GENERIC_MSI_FRAME; + gic_msi->length = sizeof(*gic_msi); + gic_msi->gic_msi_frame_id = 0; + gic_msi->base_address = cpu_to_le64(memmap[VIRT_GIC_V2M].base); + gic_msi->flags = cpu_to_le32(1); + gic_msi->spi_count = cpu_to_le16(NUM_GICV2M_SPIS); + gic_msi->spi_base = cpu_to_le16(irqmap[VIRT_GIC_V2M] + ARM_SPI_BASE); + + build_header(linker, table_data, + (void *)(table_data->data + madt_start), "APIC", + table_data->len - madt_start, 3); +} + +/* FADT */ +static void +build_fadt(GArray *table_data, GArray *linker, unsigned dsdt) +{ + AcpiFadtDescriptorRev5_1 *fadt = acpi_data_push(table_data, sizeof(*fadt)); + + /* Hardware Reduced = 1 and use PSCI 0.2+ and with HVC */ + fadt->flags = cpu_to_le32(1 << ACPI_FADT_F_HW_REDUCED_ACPI); + fadt->arm_boot_flags = cpu_to_le16((1 << ACPI_FADT_ARM_USE_PSCI_G_0_2) | + (1 << ACPI_FADT_ARM_PSCI_USE_HVC)); + + /* ACPI v5.1 (fadt->revision.fadt->minor_revision) */ + fadt->minor_revision = 0x1; + + fadt->dsdt = cpu_to_le32(dsdt); + /* DSDT address to be filled by Guest linker */ + bios_linker_loader_add_pointer(linker, ACPI_BUILD_TABLE_FILE, + ACPI_BUILD_TABLE_FILE, + table_data, &fadt->dsdt, + sizeof fadt->dsdt); + + build_header(linker, table_data, + (void *)fadt, "FACP", sizeof(*fadt), 5); +} + +/* DSDT */ +static void +build_dsdt(GArray *table_data, GArray *linker, VirtGuestInfo *guest_info) +{ + Aml *scope, *dsdt; + const MemMapEntry *memmap = guest_info->memmap; + const int *irqmap = guest_info->irqmap; + + dsdt = init_aml_allocator(); + /* Reserve space for header */ + acpi_data_push(dsdt->buf, sizeof(AcpiTableHeader)); + + scope = aml_scope("\\_SB"); + acpi_dsdt_add_cpus(scope, guest_info->smp_cpus); + acpi_dsdt_add_uart(scope, &memmap[VIRT_UART], + (irqmap[VIRT_UART] + ARM_SPI_BASE)); + acpi_dsdt_add_rtc(scope, &memmap[VIRT_RTC], + (irqmap[VIRT_RTC] + ARM_SPI_BASE)); + acpi_dsdt_add_flash(scope, &memmap[VIRT_FLASH]); + acpi_dsdt_add_virtio(scope, &memmap[VIRT_MMIO], + (irqmap[VIRT_MMIO] + ARM_SPI_BASE), NUM_VIRTIO_TRANSPORTS); + acpi_dsdt_add_pci(scope, memmap, (irqmap[VIRT_PCIE] + ARM_SPI_BASE)); + + aml_append(dsdt, scope); + + /* copy AML table into ACPI tables blob and patch header there */ + g_array_append_vals(table_data, dsdt->buf->data, dsdt->buf->len); + build_header(linker, table_data, + (void *)(table_data->data + table_data->len - dsdt->buf->len), + "DSDT", dsdt->buf->len, 2); + free_aml_allocator(); +} + +typedef +struct AcpiBuildState { + /* Copy of table in RAM (for patching). */ + MemoryRegion *table_mr; + MemoryRegion *rsdp_mr; + MemoryRegion *linker_mr; + /* Is table patched? */ + bool patched; + VirtGuestInfo *guest_info; +} AcpiBuildState; + +static +void virt_acpi_build(VirtGuestInfo *guest_info, AcpiBuildTables *tables) +{ + GArray *table_offsets; + unsigned dsdt, rsdt; + VirtAcpiCpuInfo cpuinfo; + GArray *tables_blob = tables->table_data; + + virt_acpi_get_cpu_info(&cpuinfo); + + table_offsets = g_array_new(false, true /* clear */, + sizeof(uint32_t)); + + bios_linker_loader_alloc(tables->linker, ACPI_BUILD_TABLE_FILE, + 64, false /* high memory */); + + /* + * The ACPI v5.1 tables for Hardware-reduced ACPI platform are: + * RSDP + * RSDT + * FADT + * GTDT + * MADT + * MCFG + * DSDT + */ + + /* DSDT is pointed to by FADT */ + dsdt = tables_blob->len; + build_dsdt(tables_blob, tables->linker, guest_info); + + /* FADT MADT GTDT MCFG SPCR pointed to by RSDT */ + acpi_add_table(table_offsets, tables_blob); + build_fadt(tables_blob, tables->linker, dsdt); + + acpi_add_table(table_offsets, tables_blob); + build_madt(tables_blob, tables->linker, guest_info, &cpuinfo); + + acpi_add_table(table_offsets, tables_blob); + build_gtdt(tables_blob, tables->linker); + + acpi_add_table(table_offsets, tables_blob); + build_mcfg(tables_blob, tables->linker, guest_info); + + acpi_add_table(table_offsets, tables_blob); + build_spcr(tables_blob, tables->linker, guest_info); + + /* RSDT is pointed to by RSDP */ + rsdt = tables_blob->len; + build_rsdt(tables_blob, tables->linker, table_offsets); + + /* RSDP is in FSEG memory, so allocate it separately */ + build_rsdp(tables->rsdp, tables->linker, rsdt); + + /* Cleanup memory that's no longer used. */ + g_array_free(table_offsets, true); +} + +static void acpi_ram_update(MemoryRegion *mr, GArray *data) +{ + uint32_t size = acpi_data_len(data); + + /* Make sure RAM size is correct - in case it got changed + * e.g. by migration */ + memory_region_ram_resize(mr, size, &error_abort); + + memcpy(memory_region_get_ram_ptr(mr), data->data, size); + memory_region_set_dirty(mr, 0, size); +} + +static void virt_acpi_build_update(void *build_opaque, uint32_t offset) +{ + AcpiBuildState *build_state = build_opaque; + AcpiBuildTables tables; + + /* No state to update or already patched? Nothing to do. */ + if (!build_state || build_state->patched) { + return; + } + build_state->patched = true; + + acpi_build_tables_init(&tables); + + virt_acpi_build(build_state->guest_info, &tables); + + acpi_ram_update(build_state->table_mr, tables.table_data); + acpi_ram_update(build_state->rsdp_mr, tables.rsdp); + acpi_ram_update(build_state->linker_mr, tables.linker); + + + acpi_build_tables_cleanup(&tables, true); +} + +static void virt_acpi_build_reset(void *build_opaque) +{ + AcpiBuildState *build_state = build_opaque; + build_state->patched = false; +} + +static MemoryRegion *acpi_add_rom_blob(AcpiBuildState *build_state, + GArray *blob, const char *name, + uint64_t max_size) +{ + return rom_add_blob(name, blob->data, acpi_data_len(blob), max_size, -1, + name, virt_acpi_build_update, build_state); +} + +static const VMStateDescription vmstate_virt_acpi_build = { + .name = "virt_acpi_build", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_BOOL(patched, AcpiBuildState), + VMSTATE_END_OF_LIST() + }, +}; + +void virt_acpi_setup(VirtGuestInfo *guest_info) +{ + AcpiBuildTables tables; + AcpiBuildState *build_state; + + if (!guest_info->fw_cfg) { + trace_virt_acpi_setup(); + return; + } + + if (!acpi_enabled) { + trace_virt_acpi_setup(); + return; + } + + build_state = g_malloc0(sizeof *build_state); + build_state->guest_info = guest_info; + + acpi_build_tables_init(&tables); + virt_acpi_build(build_state->guest_info, &tables); + + /* Now expose it all to Guest */ + build_state->table_mr = acpi_add_rom_blob(build_state, tables.table_data, + ACPI_BUILD_TABLE_FILE, + ACPI_BUILD_TABLE_MAX_SIZE); + assert(build_state->table_mr != NULL); + + build_state->linker_mr = + acpi_add_rom_blob(build_state, tables.linker, "etc/table-loader", 0); + + fw_cfg_add_file(guest_info->fw_cfg, ACPI_BUILD_TPMLOG_FILE, + tables.tcpalog->data, acpi_data_len(tables.tcpalog)); + + build_state->rsdp_mr = acpi_add_rom_blob(build_state, tables.rsdp, + ACPI_BUILD_RSDP_FILE, 0); + + qemu_register_reset(virt_acpi_build_reset, build_state); + virt_acpi_build_reset(build_state); + vmstate_register(NULL, 0, &vmstate_virt_acpi_build, build_state); + + /* Cleanup tables but don't free the memory: we track it + * in build_state. + */ + acpi_build_tables_cleanup(&tables, false); +} diff --git a/qemu/hw/arm/virt.c b/qemu/hw/arm/virt.c new file mode 100644 index 000000000..484689264 --- /dev/null +++ b/qemu/hw/arm/virt.c @@ -0,0 +1,977 @@ +/* + * ARM mach-virt emulation + * + * Copyright (c) 2013 Linaro Limited + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2 or later, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + * + * Emulate a virtual board which works by passing Linux all the information + * it needs about what devices are present via the device tree. + * There are some restrictions about what we can do here: + * + we can only present devices whose Linux drivers will work based + * purely on the device tree with no platform data at all + * + we want to present a very stripped-down minimalist platform, + * both because this reduces the security attack surface from the guest + * and also because it reduces our exposure to being broken when + * the kernel updates its device tree bindings and requires further + * information in a device binding that we aren't providing. + * This is essentially the same approach kvmtool uses. + */ + +#include "hw/sysbus.h" +#include "hw/arm/arm.h" +#include "hw/arm/primecell.h" +#include "hw/arm/virt.h" +#include "hw/devices.h" +#include "net/net.h" +#include "sysemu/block-backend.h" +#include "sysemu/device_tree.h" +#include "sysemu/sysemu.h" +#include "sysemu/kvm.h" +#include "hw/boards.h" +#include "hw/loader.h" +#include "exec/address-spaces.h" +#include "qemu/bitops.h" +#include "qemu/error-report.h" +#include "hw/pci-host/gpex.h" +#include "hw/arm/virt-acpi-build.h" +#include "hw/arm/sysbus-fdt.h" +#include "hw/platform-bus.h" +#include "hw/arm/fdt.h" + +/* Number of external interrupt lines to configure the GIC with */ +#define NUM_IRQS 256 + +#define PLATFORM_BUS_NUM_IRQS 64 + +static ARMPlatformBusSystemParams platform_bus_params; + +typedef struct VirtBoardInfo { + struct arm_boot_info bootinfo; + const char *cpu_model; + const MemMapEntry *memmap; + const int *irqmap; + int smp_cpus; + void *fdt; + int fdt_size; + uint32_t clock_phandle; + uint32_t gic_phandle; + uint32_t v2m_phandle; +} VirtBoardInfo; + +typedef struct { + MachineClass parent; + VirtBoardInfo *daughterboard; +} VirtMachineClass; + +typedef struct { + MachineState parent; + bool secure; +} VirtMachineState; + +#define TYPE_VIRT_MACHINE "virt" +#define VIRT_MACHINE(obj) \ + OBJECT_CHECK(VirtMachineState, (obj), TYPE_VIRT_MACHINE) +#define VIRT_MACHINE_GET_CLASS(obj) \ + OBJECT_GET_CLASS(VirtMachineClass, obj, TYPE_VIRT_MACHINE) +#define VIRT_MACHINE_CLASS(klass) \ + OBJECT_CLASS_CHECK(VirtMachineClass, klass, TYPE_VIRT_MACHINE) + +/* Addresses and sizes of our components. + * 0..128MB is space for a flash device so we can run bootrom code such as UEFI. + * 128MB..256MB is used for miscellaneous device I/O. + * 256MB..1GB is reserved for possible future PCI support (ie where the + * PCI memory window will go if we add a PCI host controller). + * 1GB and up is RAM (which may happily spill over into the + * high memory region beyond 4GB). + * This represents a compromise between how much RAM can be given to + * a 32 bit VM and leaving space for expansion and in particular for PCI. + * Note that devices should generally be placed at multiples of 0x10000, + * to accommodate guests using 64K pages. + */ +static const MemMapEntry a15memmap[] = { + /* Space up to 0x8000000 is reserved for a boot ROM */ + [VIRT_FLASH] = { 0, 0x08000000 }, + [VIRT_CPUPERIPHS] = { 0x08000000, 0x00020000 }, + /* GIC distributor and CPU interfaces sit inside the CPU peripheral space */ + [VIRT_GIC_DIST] = { 0x08000000, 0x00010000 }, + [VIRT_GIC_CPU] = { 0x08010000, 0x00010000 }, + [VIRT_GIC_V2M] = { 0x08020000, 0x00001000 }, + [VIRT_UART] = { 0x09000000, 0x00001000 }, + [VIRT_RTC] = { 0x09010000, 0x00001000 }, + [VIRT_FW_CFG] = { 0x09020000, 0x0000000a }, + [VIRT_MMIO] = { 0x0a000000, 0x00000200 }, + /* ...repeating for a total of NUM_VIRTIO_TRANSPORTS, each of that size */ + [VIRT_PLATFORM_BUS] = { 0x0c000000, 0x02000000 }, + [VIRT_PCIE_MMIO] = { 0x10000000, 0x2eff0000 }, + [VIRT_PCIE_PIO] = { 0x3eff0000, 0x00010000 }, + [VIRT_PCIE_ECAM] = { 0x3f000000, 0x01000000 }, + [VIRT_MEM] = { 0x40000000, 30ULL * 1024 * 1024 * 1024 }, +}; + +static const int a15irqmap[] = { + [VIRT_UART] = 1, + [VIRT_RTC] = 2, + [VIRT_PCIE] = 3, /* ... to 6 */ + [VIRT_MMIO] = 16, /* ...to 16 + NUM_VIRTIO_TRANSPORTS - 1 */ + [VIRT_GIC_V2M] = 48, /* ...to 48 + NUM_GICV2M_SPIS - 1 */ + [VIRT_PLATFORM_BUS] = 112, /* ...to 112 + PLATFORM_BUS_NUM_IRQS -1 */ +}; + +static VirtBoardInfo machines[] = { + { + .cpu_model = "cortex-a15", + .memmap = a15memmap, + .irqmap = a15irqmap, + }, + { + .cpu_model = "cortex-a53", + .memmap = a15memmap, + .irqmap = a15irqmap, + }, + { + .cpu_model = "cortex-a57", + .memmap = a15memmap, + .irqmap = a15irqmap, + }, + { + .cpu_model = "host", + .memmap = a15memmap, + .irqmap = a15irqmap, + }, +}; + +static VirtBoardInfo *find_machine_info(const char *cpu) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(machines); i++) { + if (strcmp(cpu, machines[i].cpu_model) == 0) { + return &machines[i]; + } + } + return NULL; +} + +static void create_fdt(VirtBoardInfo *vbi) +{ + void *fdt = create_device_tree(&vbi->fdt_size); + + if (!fdt) { + error_report("create_device_tree() failed"); + exit(1); + } + + vbi->fdt = fdt; + + /* Header */ + qemu_fdt_setprop_string(fdt, "/", "compatible", "linux,dummy-virt"); + qemu_fdt_setprop_cell(fdt, "/", "#address-cells", 0x2); + qemu_fdt_setprop_cell(fdt, "/", "#size-cells", 0x2); + + /* + * /chosen and /memory nodes must exist for load_dtb + * to fill in necessary properties later + */ + qemu_fdt_add_subnode(fdt, "/chosen"); + qemu_fdt_add_subnode(fdt, "/memory"); + qemu_fdt_setprop_string(fdt, "/memory", "device_type", "memory"); + + /* Clock node, for the benefit of the UART. The kernel device tree + * binding documentation claims the PL011 node clock properties are + * optional but in practice if you omit them the kernel refuses to + * probe for the device. + */ + vbi->clock_phandle = qemu_fdt_alloc_phandle(fdt); + qemu_fdt_add_subnode(fdt, "/apb-pclk"); + qemu_fdt_setprop_string(fdt, "/apb-pclk", "compatible", "fixed-clock"); + qemu_fdt_setprop_cell(fdt, "/apb-pclk", "#clock-cells", 0x0); + qemu_fdt_setprop_cell(fdt, "/apb-pclk", "clock-frequency", 24000000); + qemu_fdt_setprop_string(fdt, "/apb-pclk", "clock-output-names", + "clk24mhz"); + qemu_fdt_setprop_cell(fdt, "/apb-pclk", "phandle", vbi->clock_phandle); + +} + +static void fdt_add_psci_node(const VirtBoardInfo *vbi) +{ + uint32_t cpu_suspend_fn; + uint32_t cpu_off_fn; + uint32_t cpu_on_fn; + uint32_t migrate_fn; + void *fdt = vbi->fdt; + ARMCPU *armcpu = ARM_CPU(qemu_get_cpu(0)); + + qemu_fdt_add_subnode(fdt, "/psci"); + if (armcpu->psci_version == 2) { + const char comp[] = "arm,psci-0.2\0arm,psci"; + qemu_fdt_setprop(fdt, "/psci", "compatible", comp, sizeof(comp)); + + cpu_off_fn = QEMU_PSCI_0_2_FN_CPU_OFF; + if (arm_feature(&armcpu->env, ARM_FEATURE_AARCH64)) { + cpu_suspend_fn = QEMU_PSCI_0_2_FN64_CPU_SUSPEND; + cpu_on_fn = QEMU_PSCI_0_2_FN64_CPU_ON; + migrate_fn = QEMU_PSCI_0_2_FN64_MIGRATE; + } else { + cpu_suspend_fn = QEMU_PSCI_0_2_FN_CPU_SUSPEND; + cpu_on_fn = QEMU_PSCI_0_2_FN_CPU_ON; + migrate_fn = QEMU_PSCI_0_2_FN_MIGRATE; + } + } else { + qemu_fdt_setprop_string(fdt, "/psci", "compatible", "arm,psci"); + + cpu_suspend_fn = QEMU_PSCI_0_1_FN_CPU_SUSPEND; + cpu_off_fn = QEMU_PSCI_0_1_FN_CPU_OFF; + cpu_on_fn = QEMU_PSCI_0_1_FN_CPU_ON; + migrate_fn = QEMU_PSCI_0_1_FN_MIGRATE; + } + + /* We adopt the PSCI spec's nomenclature, and use 'conduit' to refer + * to the instruction that should be used to invoke PSCI functions. + * However, the device tree binding uses 'method' instead, so that is + * what we should use here. + */ + qemu_fdt_setprop_string(fdt, "/psci", "method", "hvc"); + + qemu_fdt_setprop_cell(fdt, "/psci", "cpu_suspend", cpu_suspend_fn); + qemu_fdt_setprop_cell(fdt, "/psci", "cpu_off", cpu_off_fn); + qemu_fdt_setprop_cell(fdt, "/psci", "cpu_on", cpu_on_fn); + qemu_fdt_setprop_cell(fdt, "/psci", "migrate", migrate_fn); +} + +static void fdt_add_timer_nodes(const VirtBoardInfo *vbi) +{ + /* Note that on A15 h/w these interrupts are level-triggered, + * but for the GIC implementation provided by both QEMU and KVM + * they are edge-triggered. + */ + ARMCPU *armcpu; + uint32_t irqflags = GIC_FDT_IRQ_FLAGS_EDGE_LO_HI; + + irqflags = deposit32(irqflags, GIC_FDT_IRQ_PPI_CPU_START, + GIC_FDT_IRQ_PPI_CPU_WIDTH, (1 << vbi->smp_cpus) - 1); + + qemu_fdt_add_subnode(vbi->fdt, "/timer"); + + armcpu = ARM_CPU(qemu_get_cpu(0)); + if (arm_feature(&armcpu->env, ARM_FEATURE_V8)) { + const char compat[] = "arm,armv8-timer\0arm,armv7-timer"; + qemu_fdt_setprop(vbi->fdt, "/timer", "compatible", + compat, sizeof(compat)); + } else { + qemu_fdt_setprop_string(vbi->fdt, "/timer", "compatible", + "arm,armv7-timer"); + } + qemu_fdt_setprop_cells(vbi->fdt, "/timer", "interrupts", + GIC_FDT_IRQ_TYPE_PPI, ARCH_TIMER_S_EL1_IRQ, irqflags, + GIC_FDT_IRQ_TYPE_PPI, ARCH_TIMER_NS_EL1_IRQ, irqflags, + GIC_FDT_IRQ_TYPE_PPI, ARCH_TIMER_VIRT_IRQ, irqflags, + GIC_FDT_IRQ_TYPE_PPI, ARCH_TIMER_NS_EL2_IRQ, irqflags); +} + +static void fdt_add_cpu_nodes(const VirtBoardInfo *vbi) +{ + int cpu; + + qemu_fdt_add_subnode(vbi->fdt, "/cpus"); + qemu_fdt_setprop_cell(vbi->fdt, "/cpus", "#address-cells", 0x1); + qemu_fdt_setprop_cell(vbi->fdt, "/cpus", "#size-cells", 0x0); + + for (cpu = vbi->smp_cpus - 1; cpu >= 0; cpu--) { + char *nodename = g_strdup_printf("/cpus/cpu@%d", cpu); + ARMCPU *armcpu = ARM_CPU(qemu_get_cpu(cpu)); + + qemu_fdt_add_subnode(vbi->fdt, nodename); + qemu_fdt_setprop_string(vbi->fdt, nodename, "device_type", "cpu"); + qemu_fdt_setprop_string(vbi->fdt, nodename, "compatible", + armcpu->dtb_compatible); + + if (vbi->smp_cpus > 1) { + qemu_fdt_setprop_string(vbi->fdt, nodename, + "enable-method", "psci"); + } + + qemu_fdt_setprop_cell(vbi->fdt, nodename, "reg", armcpu->mp_affinity); + g_free(nodename); + } +} + +static void fdt_add_v2m_gic_node(VirtBoardInfo *vbi) +{ + vbi->v2m_phandle = qemu_fdt_alloc_phandle(vbi->fdt); + qemu_fdt_add_subnode(vbi->fdt, "/intc/v2m"); + qemu_fdt_setprop_string(vbi->fdt, "/intc/v2m", "compatible", + "arm,gic-v2m-frame"); + qemu_fdt_setprop(vbi->fdt, "/intc/v2m", "msi-controller", NULL, 0); + qemu_fdt_setprop_sized_cells(vbi->fdt, "/intc/v2m", "reg", + 2, vbi->memmap[VIRT_GIC_V2M].base, + 2, vbi->memmap[VIRT_GIC_V2M].size); + qemu_fdt_setprop_cell(vbi->fdt, "/intc/v2m", "phandle", vbi->v2m_phandle); +} + +static void fdt_add_gic_node(VirtBoardInfo *vbi) +{ + vbi->gic_phandle = qemu_fdt_alloc_phandle(vbi->fdt); + qemu_fdt_setprop_cell(vbi->fdt, "/", "interrupt-parent", vbi->gic_phandle); + + qemu_fdt_add_subnode(vbi->fdt, "/intc"); + /* 'cortex-a15-gic' means 'GIC v2' */ + qemu_fdt_setprop_string(vbi->fdt, "/intc", "compatible", + "arm,cortex-a15-gic"); + qemu_fdt_setprop_cell(vbi->fdt, "/intc", "#interrupt-cells", 3); + qemu_fdt_setprop(vbi->fdt, "/intc", "interrupt-controller", NULL, 0); + qemu_fdt_setprop_sized_cells(vbi->fdt, "/intc", "reg", + 2, vbi->memmap[VIRT_GIC_DIST].base, + 2, vbi->memmap[VIRT_GIC_DIST].size, + 2, vbi->memmap[VIRT_GIC_CPU].base, + 2, vbi->memmap[VIRT_GIC_CPU].size); + qemu_fdt_setprop_cell(vbi->fdt, "/intc", "#address-cells", 0x2); + qemu_fdt_setprop_cell(vbi->fdt, "/intc", "#size-cells", 0x2); + qemu_fdt_setprop(vbi->fdt, "/intc", "ranges", NULL, 0); + qemu_fdt_setprop_cell(vbi->fdt, "/intc", "phandle", vbi->gic_phandle); +} + +static void create_v2m(VirtBoardInfo *vbi, qemu_irq *pic) +{ + int i; + int irq = vbi->irqmap[VIRT_GIC_V2M]; + DeviceState *dev; + + dev = qdev_create(NULL, "arm-gicv2m"); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, vbi->memmap[VIRT_GIC_V2M].base); + qdev_prop_set_uint32(dev, "base-spi", irq); + qdev_prop_set_uint32(dev, "num-spi", NUM_GICV2M_SPIS); + qdev_init_nofail(dev); + + for (i = 0; i < NUM_GICV2M_SPIS; i++) { + sysbus_connect_irq(SYS_BUS_DEVICE(dev), i, pic[irq + i]); + } + + fdt_add_v2m_gic_node(vbi); +} + +static void create_gic(VirtBoardInfo *vbi, qemu_irq *pic) +{ + /* We create a standalone GIC v2 */ + DeviceState *gicdev; + SysBusDevice *gicbusdev; + const char *gictype = "arm_gic"; + int i; + + if (kvm_irqchip_in_kernel()) { + gictype = "kvm-arm-gic"; + } + + gicdev = qdev_create(NULL, gictype); + qdev_prop_set_uint32(gicdev, "revision", 2); + qdev_prop_set_uint32(gicdev, "num-cpu", smp_cpus); + /* Note that the num-irq property counts both internal and external + * interrupts; there are always 32 of the former (mandated by GIC spec). + */ + qdev_prop_set_uint32(gicdev, "num-irq", NUM_IRQS + 32); + qdev_init_nofail(gicdev); + gicbusdev = SYS_BUS_DEVICE(gicdev); + sysbus_mmio_map(gicbusdev, 0, vbi->memmap[VIRT_GIC_DIST].base); + sysbus_mmio_map(gicbusdev, 1, vbi->memmap[VIRT_GIC_CPU].base); + + /* Wire the outputs from each CPU's generic timer to the + * appropriate GIC PPI inputs, and the GIC's IRQ output to + * the CPU's IRQ input. + */ + for (i = 0; i < smp_cpus; i++) { + DeviceState *cpudev = DEVICE(qemu_get_cpu(i)); + int ppibase = NUM_IRQS + i * 32; + /* physical timer; we wire it up to the non-secure timer's ID, + * since a real A15 always has TrustZone but QEMU doesn't. + */ + qdev_connect_gpio_out(cpudev, 0, + qdev_get_gpio_in(gicdev, ppibase + 30)); + /* virtual timer */ + qdev_connect_gpio_out(cpudev, 1, + qdev_get_gpio_in(gicdev, ppibase + 27)); + + sysbus_connect_irq(gicbusdev, i, qdev_get_gpio_in(cpudev, ARM_CPU_IRQ)); + sysbus_connect_irq(gicbusdev, i + smp_cpus, + qdev_get_gpio_in(cpudev, ARM_CPU_FIQ)); + } + + for (i = 0; i < NUM_IRQS; i++) { + pic[i] = qdev_get_gpio_in(gicdev, i); + } + + fdt_add_gic_node(vbi); + + create_v2m(vbi, pic); +} + +static void create_uart(const VirtBoardInfo *vbi, qemu_irq *pic) +{ + char *nodename; + hwaddr base = vbi->memmap[VIRT_UART].base; + hwaddr size = vbi->memmap[VIRT_UART].size; + int irq = vbi->irqmap[VIRT_UART]; + const char compat[] = "arm,pl011\0arm,primecell"; + const char clocknames[] = "uartclk\0apb_pclk"; + + sysbus_create_simple("pl011", base, pic[irq]); + + nodename = g_strdup_printf("/pl011@%" PRIx64, base); + qemu_fdt_add_subnode(vbi->fdt, nodename); + /* Note that we can't use setprop_string because of the embedded NUL */ + qemu_fdt_setprop(vbi->fdt, nodename, "compatible", + compat, sizeof(compat)); + qemu_fdt_setprop_sized_cells(vbi->fdt, nodename, "reg", + 2, base, 2, size); + qemu_fdt_setprop_cells(vbi->fdt, nodename, "interrupts", + GIC_FDT_IRQ_TYPE_SPI, irq, + GIC_FDT_IRQ_FLAGS_LEVEL_HI); + qemu_fdt_setprop_cells(vbi->fdt, nodename, "clocks", + vbi->clock_phandle, vbi->clock_phandle); + qemu_fdt_setprop(vbi->fdt, nodename, "clock-names", + clocknames, sizeof(clocknames)); + + qemu_fdt_setprop_string(vbi->fdt, "/chosen", "stdout-path", nodename); + g_free(nodename); +} + +static void create_rtc(const VirtBoardInfo *vbi, qemu_irq *pic) +{ + char *nodename; + hwaddr base = vbi->memmap[VIRT_RTC].base; + hwaddr size = vbi->memmap[VIRT_RTC].size; + int irq = vbi->irqmap[VIRT_RTC]; + const char compat[] = "arm,pl031\0arm,primecell"; + + sysbus_create_simple("pl031", base, pic[irq]); + + nodename = g_strdup_printf("/pl031@%" PRIx64, base); + qemu_fdt_add_subnode(vbi->fdt, nodename); + qemu_fdt_setprop(vbi->fdt, nodename, "compatible", compat, sizeof(compat)); + qemu_fdt_setprop_sized_cells(vbi->fdt, nodename, "reg", + 2, base, 2, size); + qemu_fdt_setprop_cells(vbi->fdt, nodename, "interrupts", + GIC_FDT_IRQ_TYPE_SPI, irq, + GIC_FDT_IRQ_FLAGS_LEVEL_HI); + qemu_fdt_setprop_cell(vbi->fdt, nodename, "clocks", vbi->clock_phandle); + qemu_fdt_setprop_string(vbi->fdt, nodename, "clock-names", "apb_pclk"); + g_free(nodename); +} + +static void create_virtio_devices(const VirtBoardInfo *vbi, qemu_irq *pic) +{ + int i; + hwaddr size = vbi->memmap[VIRT_MMIO].size; + + /* We create the transports in forwards order. Since qbus_realize() + * prepends (not appends) new child buses, the incrementing loop below will + * create a list of virtio-mmio buses with decreasing base addresses. + * + * When a -device option is processed from the command line, + * qbus_find_recursive() picks the next free virtio-mmio bus in forwards + * order. The upshot is that -device options in increasing command line + * order are mapped to virtio-mmio buses with decreasing base addresses. + * + * When this code was originally written, that arrangement ensured that the + * guest Linux kernel would give the lowest "name" (/dev/vda, eth0, etc) to + * the first -device on the command line. (The end-to-end order is a + * function of this loop, qbus_realize(), qbus_find_recursive(), and the + * guest kernel's name-to-address assignment strategy.) + * + * Meanwhile, the kernel's traversal seems to have been reversed; see eg. + * the message, if not necessarily the code, of commit 70161ff336. + * Therefore the loop now establishes the inverse of the original intent. + * + * Unfortunately, we can't counteract the kernel change by reversing the + * loop; it would break existing command lines. + * + * In any case, the kernel makes no guarantee about the stability of + * enumeration order of virtio devices (as demonstrated by it changing + * between kernel versions). For reliable and stable identification + * of disks users must use UUIDs or similar mechanisms. + */ + for (i = 0; i < NUM_VIRTIO_TRANSPORTS; i++) { + int irq = vbi->irqmap[VIRT_MMIO] + i; + hwaddr base = vbi->memmap[VIRT_MMIO].base + i * size; + + sysbus_create_simple("virtio-mmio", base, pic[irq]); + } + + /* We add dtb nodes in reverse order so that they appear in the finished + * device tree lowest address first. + * + * Note that this mapping is independent of the loop above. The previous + * loop influences virtio device to virtio transport assignment, whereas + * this loop controls how virtio transports are laid out in the dtb. + */ + for (i = NUM_VIRTIO_TRANSPORTS - 1; i >= 0; i--) { + char *nodename; + int irq = vbi->irqmap[VIRT_MMIO] + i; + hwaddr base = vbi->memmap[VIRT_MMIO].base + i * size; + + nodename = g_strdup_printf("/virtio_mmio@%" PRIx64, base); + qemu_fdt_add_subnode(vbi->fdt, nodename); + qemu_fdt_setprop_string(vbi->fdt, nodename, + "compatible", "virtio,mmio"); + qemu_fdt_setprop_sized_cells(vbi->fdt, nodename, "reg", + 2, base, 2, size); + qemu_fdt_setprop_cells(vbi->fdt, nodename, "interrupts", + GIC_FDT_IRQ_TYPE_SPI, irq, + GIC_FDT_IRQ_FLAGS_EDGE_LO_HI); + g_free(nodename); + } +} + +static void create_one_flash(const char *name, hwaddr flashbase, + hwaddr flashsize) +{ + /* Create and map a single flash device. We use the same + * parameters as the flash devices on the Versatile Express board. + */ + DriveInfo *dinfo = drive_get_next(IF_PFLASH); + DeviceState *dev = qdev_create(NULL, "cfi.pflash01"); + const uint64_t sectorlength = 256 * 1024; + + if (dinfo) { + qdev_prop_set_drive(dev, "drive", blk_by_legacy_dinfo(dinfo), + &error_abort); + } + + qdev_prop_set_uint32(dev, "num-blocks", flashsize / sectorlength); + qdev_prop_set_uint64(dev, "sector-length", sectorlength); + qdev_prop_set_uint8(dev, "width", 4); + qdev_prop_set_uint8(dev, "device-width", 2); + qdev_prop_set_bit(dev, "big-endian", false); + qdev_prop_set_uint16(dev, "id0", 0x89); + qdev_prop_set_uint16(dev, "id1", 0x18); + qdev_prop_set_uint16(dev, "id2", 0x00); + qdev_prop_set_uint16(dev, "id3", 0x00); + qdev_prop_set_string(dev, "name", name); + qdev_init_nofail(dev); + + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, flashbase); +} + +static void create_flash(const VirtBoardInfo *vbi) +{ + /* Create two flash devices to fill the VIRT_FLASH space in the memmap. + * Any file passed via -bios goes in the first of these. + */ + hwaddr flashsize = vbi->memmap[VIRT_FLASH].size / 2; + hwaddr flashbase = vbi->memmap[VIRT_FLASH].base; + char *nodename; + + if (bios_name) { + char *fn; + int image_size; + + if (drive_get(IF_PFLASH, 0, 0)) { + error_report("The contents of the first flash device may be " + "specified with -bios or with -drive if=pflash... " + "but you cannot use both options at once"); + exit(1); + } + fn = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); + if (!fn) { + error_report("Could not find ROM image '%s'", bios_name); + exit(1); + } + image_size = load_image_targphys(fn, flashbase, flashsize); + g_free(fn); + if (image_size < 0) { + error_report("Could not load ROM image '%s'", bios_name); + exit(1); + } + } + + create_one_flash("virt.flash0", flashbase, flashsize); + create_one_flash("virt.flash1", flashbase + flashsize, flashsize); + + nodename = g_strdup_printf("/flash@%" PRIx64, flashbase); + qemu_fdt_add_subnode(vbi->fdt, nodename); + qemu_fdt_setprop_string(vbi->fdt, nodename, "compatible", "cfi-flash"); + qemu_fdt_setprop_sized_cells(vbi->fdt, nodename, "reg", + 2, flashbase, 2, flashsize, + 2, flashbase + flashsize, 2, flashsize); + qemu_fdt_setprop_cell(vbi->fdt, nodename, "bank-width", 4); + g_free(nodename); +} + +static void create_fw_cfg(const VirtBoardInfo *vbi) +{ + hwaddr base = vbi->memmap[VIRT_FW_CFG].base; + hwaddr size = vbi->memmap[VIRT_FW_CFG].size; + char *nodename; + + fw_cfg_init_mem_wide(base + 8, base, 8); + + nodename = g_strdup_printf("/fw-cfg@%" PRIx64, base); + qemu_fdt_add_subnode(vbi->fdt, nodename); + qemu_fdt_setprop_string(vbi->fdt, nodename, + "compatible", "qemu,fw-cfg-mmio"); + qemu_fdt_setprop_sized_cells(vbi->fdt, nodename, "reg", + 2, base, 2, size); + g_free(nodename); +} + +static void create_pcie_irq_map(const VirtBoardInfo *vbi, uint32_t gic_phandle, + int first_irq, const char *nodename) +{ + int devfn, pin; + uint32_t full_irq_map[4 * 4 * 10] = { 0 }; + uint32_t *irq_map = full_irq_map; + + for (devfn = 0; devfn <= 0x18; devfn += 0x8) { + for (pin = 0; pin < 4; pin++) { + int irq_type = GIC_FDT_IRQ_TYPE_SPI; + int irq_nr = first_irq + ((pin + PCI_SLOT(devfn)) % PCI_NUM_PINS); + int irq_level = GIC_FDT_IRQ_FLAGS_LEVEL_HI; + int i; + + uint32_t map[] = { + devfn << 8, 0, 0, /* devfn */ + pin + 1, /* PCI pin */ + gic_phandle, 0, 0, irq_type, irq_nr, irq_level }; /* GIC irq */ + + /* Convert map to big endian */ + for (i = 0; i < 10; i++) { + irq_map[i] = cpu_to_be32(map[i]); + } + irq_map += 10; + } + } + + qemu_fdt_setprop(vbi->fdt, nodename, "interrupt-map", + full_irq_map, sizeof(full_irq_map)); + + qemu_fdt_setprop_cells(vbi->fdt, nodename, "interrupt-map-mask", + 0x1800, 0, 0, /* devfn (PCI_SLOT(3)) */ + 0x7 /* PCI irq */); +} + +static void create_pcie(const VirtBoardInfo *vbi, qemu_irq *pic) +{ + hwaddr base_mmio = vbi->memmap[VIRT_PCIE_MMIO].base; + hwaddr size_mmio = vbi->memmap[VIRT_PCIE_MMIO].size; + hwaddr base_pio = vbi->memmap[VIRT_PCIE_PIO].base; + hwaddr size_pio = vbi->memmap[VIRT_PCIE_PIO].size; + hwaddr base_ecam = vbi->memmap[VIRT_PCIE_ECAM].base; + hwaddr size_ecam = vbi->memmap[VIRT_PCIE_ECAM].size; + hwaddr base = base_mmio; + int nr_pcie_buses = size_ecam / PCIE_MMCFG_SIZE_MIN; + int irq = vbi->irqmap[VIRT_PCIE]; + MemoryRegion *mmio_alias; + MemoryRegion *mmio_reg; + MemoryRegion *ecam_alias; + MemoryRegion *ecam_reg; + DeviceState *dev; + char *nodename; + int i; + + dev = qdev_create(NULL, TYPE_GPEX_HOST); + qdev_init_nofail(dev); + + /* Map only the first size_ecam bytes of ECAM space */ + ecam_alias = g_new0(MemoryRegion, 1); + ecam_reg = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 0); + memory_region_init_alias(ecam_alias, OBJECT(dev), "pcie-ecam", + ecam_reg, 0, size_ecam); + memory_region_add_subregion(get_system_memory(), base_ecam, ecam_alias); + + /* Map the MMIO window into system address space so as to expose + * the section of PCI MMIO space which starts at the same base address + * (ie 1:1 mapping for that part of PCI MMIO space visible through + * the window). + */ + mmio_alias = g_new0(MemoryRegion, 1); + mmio_reg = sysbus_mmio_get_region(SYS_BUS_DEVICE(dev), 1); + memory_region_init_alias(mmio_alias, OBJECT(dev), "pcie-mmio", + mmio_reg, base_mmio, size_mmio); + memory_region_add_subregion(get_system_memory(), base_mmio, mmio_alias); + + /* Map IO port space */ + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 2, base_pio); + + for (i = 0; i < GPEX_NUM_IRQS; i++) { + sysbus_connect_irq(SYS_BUS_DEVICE(dev), i, pic[irq + i]); + } + + nodename = g_strdup_printf("/pcie@%" PRIx64, base); + qemu_fdt_add_subnode(vbi->fdt, nodename); + qemu_fdt_setprop_string(vbi->fdt, nodename, + "compatible", "pci-host-ecam-generic"); + qemu_fdt_setprop_string(vbi->fdt, nodename, "device_type", "pci"); + qemu_fdt_setprop_cell(vbi->fdt, nodename, "#address-cells", 3); + qemu_fdt_setprop_cell(vbi->fdt, nodename, "#size-cells", 2); + qemu_fdt_setprop_cells(vbi->fdt, nodename, "bus-range", 0, + nr_pcie_buses - 1); + + qemu_fdt_setprop_cells(vbi->fdt, nodename, "msi-parent", vbi->v2m_phandle); + + qemu_fdt_setprop_sized_cells(vbi->fdt, nodename, "reg", + 2, base_ecam, 2, size_ecam); + qemu_fdt_setprop_sized_cells(vbi->fdt, nodename, "ranges", + 1, FDT_PCI_RANGE_IOPORT, 2, 0, + 2, base_pio, 2, size_pio, + 1, FDT_PCI_RANGE_MMIO, 2, base_mmio, + 2, base_mmio, 2, size_mmio); + + qemu_fdt_setprop_cell(vbi->fdt, nodename, "#interrupt-cells", 1); + create_pcie_irq_map(vbi, vbi->gic_phandle, irq, nodename); + + g_free(nodename); +} + +static void create_platform_bus(VirtBoardInfo *vbi, qemu_irq *pic) +{ + DeviceState *dev; + SysBusDevice *s; + int i; + ARMPlatformBusFDTParams *fdt_params = g_new(ARMPlatformBusFDTParams, 1); + MemoryRegion *sysmem = get_system_memory(); + + platform_bus_params.platform_bus_base = vbi->memmap[VIRT_PLATFORM_BUS].base; + platform_bus_params.platform_bus_size = vbi->memmap[VIRT_PLATFORM_BUS].size; + platform_bus_params.platform_bus_first_irq = vbi->irqmap[VIRT_PLATFORM_BUS]; + platform_bus_params.platform_bus_num_irqs = PLATFORM_BUS_NUM_IRQS; + + fdt_params->system_params = &platform_bus_params; + fdt_params->binfo = &vbi->bootinfo; + fdt_params->intc = "/intc"; + /* + * register a machine init done notifier that creates the device tree + * nodes of the platform bus and its children dynamic sysbus devices + */ + arm_register_platform_bus_fdt_creator(fdt_params); + + dev = qdev_create(NULL, TYPE_PLATFORM_BUS_DEVICE); + dev->id = TYPE_PLATFORM_BUS_DEVICE; + qdev_prop_set_uint32(dev, "num_irqs", + platform_bus_params.platform_bus_num_irqs); + qdev_prop_set_uint32(dev, "mmio_size", + platform_bus_params.platform_bus_size); + qdev_init_nofail(dev); + s = SYS_BUS_DEVICE(dev); + + for (i = 0; i < platform_bus_params.platform_bus_num_irqs; i++) { + int irqn = platform_bus_params.platform_bus_first_irq + i; + sysbus_connect_irq(s, i, pic[irqn]); + } + + memory_region_add_subregion(sysmem, + platform_bus_params.platform_bus_base, + sysbus_mmio_get_region(s, 0)); +} + +static void *machvirt_dtb(const struct arm_boot_info *binfo, int *fdt_size) +{ + const VirtBoardInfo *board = (const VirtBoardInfo *)binfo; + + *fdt_size = board->fdt_size; + return board->fdt; +} + +static +void virt_guest_info_machine_done(Notifier *notifier, void *data) +{ + VirtGuestInfoState *guest_info_state = container_of(notifier, + VirtGuestInfoState, machine_done); + virt_acpi_setup(&guest_info_state->info); +} + +static void machvirt_init(MachineState *machine) +{ + VirtMachineState *vms = VIRT_MACHINE(machine); + qemu_irq pic[NUM_IRQS]; + MemoryRegion *sysmem = get_system_memory(); + int n; + MemoryRegion *ram = g_new(MemoryRegion, 1); + const char *cpu_model = machine->cpu_model; + VirtBoardInfo *vbi; + VirtGuestInfoState *guest_info_state = g_malloc0(sizeof *guest_info_state); + VirtGuestInfo *guest_info = &guest_info_state->info; + char **cpustr; + + if (!cpu_model) { + cpu_model = "cortex-a15"; + } + + /* Separate the actual CPU model name from any appended features */ + cpustr = g_strsplit(cpu_model, ",", 2); + + vbi = find_machine_info(cpustr[0]); + + if (!vbi) { + error_report("mach-virt: CPU %s not supported", cpustr[0]); + exit(1); + } + + vbi->smp_cpus = smp_cpus; + + if (machine->ram_size > vbi->memmap[VIRT_MEM].size) { + error_report("mach-virt: cannot model more than 30GB RAM"); + exit(1); + } + + create_fdt(vbi); + + for (n = 0; n < smp_cpus; n++) { + ObjectClass *oc = cpu_class_by_name(TYPE_ARM_CPU, cpustr[0]); + CPUClass *cc = CPU_CLASS(oc); + Object *cpuobj; + Error *err = NULL; + char *cpuopts = g_strdup(cpustr[1]); + + if (!oc) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + cpuobj = object_new(object_class_get_name(oc)); + + /* Handle any CPU options specified by the user */ + cc->parse_features(CPU(cpuobj), cpuopts, &err); + g_free(cpuopts); + if (err) { + error_report_err(err); + exit(1); + } + + if (!vms->secure) { + object_property_set_bool(cpuobj, false, "has_el3", NULL); + } + + object_property_set_int(cpuobj, QEMU_PSCI_CONDUIT_HVC, "psci-conduit", + NULL); + + /* Secondary CPUs start in PSCI powered-down state */ + if (n > 0) { + object_property_set_bool(cpuobj, true, "start-powered-off", NULL); + } + + if (object_property_find(cpuobj, "reset-cbar", NULL)) { + object_property_set_int(cpuobj, vbi->memmap[VIRT_CPUPERIPHS].base, + "reset-cbar", &error_abort); + } + + object_property_set_bool(cpuobj, true, "realized", NULL); + } + g_strfreev(cpustr); + fdt_add_timer_nodes(vbi); + fdt_add_cpu_nodes(vbi); + fdt_add_psci_node(vbi); + + memory_region_allocate_system_memory(ram, NULL, "mach-virt.ram", + machine->ram_size); + memory_region_add_subregion(sysmem, vbi->memmap[VIRT_MEM].base, ram); + + create_flash(vbi); + + create_gic(vbi, pic); + + create_uart(vbi, pic); + + create_rtc(vbi, pic); + + create_pcie(vbi, pic); + + /* Create mmio transports, so the user can create virtio backends + * (which will be automatically plugged in to the transports). If + * no backend is created the transport will just sit harmlessly idle. + */ + create_virtio_devices(vbi, pic); + + create_fw_cfg(vbi); + rom_set_fw(fw_cfg_find()); + + guest_info->smp_cpus = smp_cpus; + guest_info->fw_cfg = fw_cfg_find(); + guest_info->memmap = vbi->memmap; + guest_info->irqmap = vbi->irqmap; + guest_info_state->machine_done.notify = virt_guest_info_machine_done; + qemu_add_machine_init_done_notifier(&guest_info_state->machine_done); + + vbi->bootinfo.ram_size = machine->ram_size; + vbi->bootinfo.kernel_filename = machine->kernel_filename; + vbi->bootinfo.kernel_cmdline = machine->kernel_cmdline; + vbi->bootinfo.initrd_filename = machine->initrd_filename; + vbi->bootinfo.nb_cpus = smp_cpus; + vbi->bootinfo.board_id = -1; + vbi->bootinfo.loader_start = vbi->memmap[VIRT_MEM].base; + vbi->bootinfo.get_dtb = machvirt_dtb; + vbi->bootinfo.firmware_loaded = bios_name || drive_get(IF_PFLASH, 0, 0); + arm_load_kernel(ARM_CPU(first_cpu), &vbi->bootinfo); + + /* + * arm_load_kernel machine init done notifier registration must + * happen before the platform_bus_create call. In this latter, + * another notifier is registered which adds platform bus nodes. + * Notifiers are executed in registration reverse order. + */ + create_platform_bus(vbi, pic); +} + +static bool virt_get_secure(Object *obj, Error **errp) +{ + VirtMachineState *vms = VIRT_MACHINE(obj); + + return vms->secure; +} + +static void virt_set_secure(Object *obj, bool value, Error **errp) +{ + VirtMachineState *vms = VIRT_MACHINE(obj); + + vms->secure = value; +} + +static void virt_instance_init(Object *obj) +{ + VirtMachineState *vms = VIRT_MACHINE(obj); + + /* EL3 is enabled by default on virt */ + vms->secure = true; + object_property_add_bool(obj, "secure", virt_get_secure, + virt_set_secure, NULL); + object_property_set_description(obj, "secure", + "Set on/off to enable/disable the ARM " + "Security Extensions (TrustZone)", + NULL); +} + +static void virt_class_init(ObjectClass *oc, void *data) +{ + MachineClass *mc = MACHINE_CLASS(oc); + + mc->name = TYPE_VIRT_MACHINE; + mc->desc = "ARM Virtual Machine", + mc->init = machvirt_init; + mc->max_cpus = 8; + mc->has_dynamic_sysbus = true; + mc->block_default_type = IF_VIRTIO; + mc->no_cdrom = 1; +} + +static const TypeInfo machvirt_info = { + .name = TYPE_VIRT_MACHINE, + .parent = TYPE_MACHINE, + .instance_size = sizeof(VirtMachineState), + .instance_init = virt_instance_init, + .class_size = sizeof(VirtMachineClass), + .class_init = virt_class_init, +}; + +static void machvirt_machine_init(void) +{ + type_register_static(&machvirt_info); +} + +machine_init(machvirt_machine_init); diff --git a/qemu/hw/arm/xilinx_zynq.c b/qemu/hw/arm/xilinx_zynq.c new file mode 100644 index 000000000..a4e7b5c63 --- /dev/null +++ b/qemu/hw/arm/xilinx_zynq.c @@ -0,0 +1,272 @@ +/* + * Xilinx Zynq Baseboard System emulation. + * + * Copyright (c) 2010 Xilinx. + * Copyright (c) 2012 Peter A.G. Crosthwaite (peter.croshtwaite@petalogix.com) + * Copyright (c) 2012 Petalogix Pty Ltd. + * Written by Haibing Ma + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw/sysbus.h" +#include "hw/arm/arm.h" +#include "net/net.h" +#include "exec/address-spaces.h" +#include "sysemu/sysemu.h" +#include "hw/boards.h" +#include "hw/block/flash.h" +#include "sysemu/block-backend.h" +#include "hw/loader.h" +#include "hw/ssi.h" +#include "qemu/error-report.h" + +#define NUM_SPI_FLASHES 4 +#define NUM_QSPI_FLASHES 2 +#define NUM_QSPI_BUSSES 2 + +#define FLASH_SIZE (64 * 1024 * 1024) +#define FLASH_SECTOR_SIZE (128 * 1024) + +#define IRQ_OFFSET 32 /* pic interrupts start from index 32 */ + +#define MPCORE_PERIPHBASE 0xF8F00000 +#define ZYNQ_BOARD_MIDR 0x413FC090 + +static const int dma_irqs[8] = { + 46, 47, 48, 49, 72, 73, 74, 75 +}; + +static struct arm_boot_info zynq_binfo = {}; + +static void gem_init(NICInfo *nd, uint32_t base, qemu_irq irq) +{ + DeviceState *dev; + SysBusDevice *s; + + dev = qdev_create(NULL, "cadence_gem"); + if (nd->used) { + qemu_check_nic_model(nd, "cadence_gem"); + qdev_set_nic_properties(dev, nd); + } + qdev_init_nofail(dev); + s = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(s, 0, base); + sysbus_connect_irq(s, 0, irq); +} + +static inline void zynq_init_spi_flashes(uint32_t base_addr, qemu_irq irq, + bool is_qspi) +{ + DeviceState *dev; + SysBusDevice *busdev; + SSIBus *spi; + DeviceState *flash_dev; + int i, j; + int num_busses = is_qspi ? NUM_QSPI_BUSSES : 1; + int num_ss = is_qspi ? NUM_QSPI_FLASHES : NUM_SPI_FLASHES; + + dev = qdev_create(NULL, is_qspi ? "xlnx.ps7-qspi" : "xlnx.ps7-spi"); + qdev_prop_set_uint8(dev, "num-txrx-bytes", is_qspi ? 4 : 1); + qdev_prop_set_uint8(dev, "num-ss-bits", num_ss); + qdev_prop_set_uint8(dev, "num-busses", num_busses); + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(busdev, 0, base_addr); + if (is_qspi) { + sysbus_mmio_map(busdev, 1, 0xFC000000); + } + sysbus_connect_irq(busdev, 0, irq); + + for (i = 0; i < num_busses; ++i) { + char bus_name[16]; + qemu_irq cs_line; + + snprintf(bus_name, 16, "spi%d", i); + spi = (SSIBus *)qdev_get_child_bus(dev, bus_name); + + for (j = 0; j < num_ss; ++j) { + flash_dev = ssi_create_slave(spi, "n25q128"); + + cs_line = qdev_get_gpio_in_named(flash_dev, SSI_GPIO_CS, 0); + sysbus_connect_irq(busdev, i * num_ss + j + 1, cs_line); + } + } + +} + +static void zynq_init(MachineState *machine) +{ + ram_addr_t ram_size = machine->ram_size; + const char *cpu_model = machine->cpu_model; + const char *kernel_filename = machine->kernel_filename; + const char *kernel_cmdline = machine->kernel_cmdline; + const char *initrd_filename = machine->initrd_filename; + ObjectClass *cpu_oc; + ARMCPU *cpu; + MemoryRegion *address_space_mem = get_system_memory(); + MemoryRegion *ext_ram = g_new(MemoryRegion, 1); + MemoryRegion *ocm_ram = g_new(MemoryRegion, 1); + DeviceState *dev; + SysBusDevice *busdev; + qemu_irq pic[64]; + Error *err = NULL; + int n; + + if (!cpu_model) { + cpu_model = "cortex-a9"; + } + cpu_oc = cpu_class_by_name(TYPE_ARM_CPU, cpu_model); + + cpu = ARM_CPU(object_new(object_class_get_name(cpu_oc))); + + /* By default A9 CPUs have EL3 enabled. This board does not + * currently support EL3 so the CPU EL3 property is disabled before + * realization. + */ + if (object_property_find(OBJECT(cpu), "has_el3", NULL)) { + object_property_set_bool(OBJECT(cpu), false, "has_el3", &err); + if (err) { + error_report_err(err); + exit(1); + } + } + + object_property_set_int(OBJECT(cpu), ZYNQ_BOARD_MIDR, "midr", &err); + if (err) { + error_report_err(err); + exit(1); + } + + object_property_set_int(OBJECT(cpu), MPCORE_PERIPHBASE, "reset-cbar", &err); + if (err) { + error_report_err(err); + exit(1); + } + object_property_set_bool(OBJECT(cpu), true, "realized", &err); + if (err) { + error_report_err(err); + exit(1); + } + + /* max 2GB ram */ + if (ram_size > 0x80000000) { + ram_size = 0x80000000; + } + + /* DDR remapped to address zero. */ + memory_region_allocate_system_memory(ext_ram, NULL, "zynq.ext_ram", + ram_size); + memory_region_add_subregion(address_space_mem, 0, ext_ram); + + /* 256K of on-chip memory */ + memory_region_init_ram(ocm_ram, NULL, "zynq.ocm_ram", 256 << 10, + &error_abort); + vmstate_register_ram_global(ocm_ram); + memory_region_add_subregion(address_space_mem, 0xFFFC0000, ocm_ram); + + DriveInfo *dinfo = drive_get(IF_PFLASH, 0, 0); + + /* AMD */ + pflash_cfi02_register(0xe2000000, NULL, "zynq.pflash", FLASH_SIZE, + dinfo ? blk_by_legacy_dinfo(dinfo) : NULL, + FLASH_SECTOR_SIZE, + FLASH_SIZE/FLASH_SECTOR_SIZE, 1, + 1, 0x0066, 0x0022, 0x0000, 0x0000, 0x0555, 0x2aa, + 0); + + dev = qdev_create(NULL, "xilinx,zynq_slcr"); + qdev_init_nofail(dev); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xF8000000); + + dev = qdev_create(NULL, "a9mpcore_priv"); + qdev_prop_set_uint32(dev, "num-cpu", 1); + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(busdev, 0, MPCORE_PERIPHBASE); + sysbus_connect_irq(busdev, 0, + qdev_get_gpio_in(DEVICE(cpu), ARM_CPU_IRQ)); + + for (n = 0; n < 64; n++) { + pic[n] = qdev_get_gpio_in(dev, n); + } + + zynq_init_spi_flashes(0xE0006000, pic[58-IRQ_OFFSET], false); + zynq_init_spi_flashes(0xE0007000, pic[81-IRQ_OFFSET], false); + zynq_init_spi_flashes(0xE000D000, pic[51-IRQ_OFFSET], true); + + sysbus_create_simple("xlnx,ps7-usb", 0xE0002000, pic[53-IRQ_OFFSET]); + sysbus_create_simple("xlnx,ps7-usb", 0xE0003000, pic[76-IRQ_OFFSET]); + + sysbus_create_simple("cadence_uart", 0xE0000000, pic[59-IRQ_OFFSET]); + sysbus_create_simple("cadence_uart", 0xE0001000, pic[82-IRQ_OFFSET]); + + sysbus_create_varargs("cadence_ttc", 0xF8001000, + pic[42-IRQ_OFFSET], pic[43-IRQ_OFFSET], pic[44-IRQ_OFFSET], NULL); + sysbus_create_varargs("cadence_ttc", 0xF8002000, + pic[69-IRQ_OFFSET], pic[70-IRQ_OFFSET], pic[71-IRQ_OFFSET], NULL); + + gem_init(&nd_table[0], 0xE000B000, pic[54-IRQ_OFFSET]); + gem_init(&nd_table[1], 0xE000C000, pic[77-IRQ_OFFSET]); + + dev = qdev_create(NULL, "generic-sdhci"); + qdev_init_nofail(dev); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xE0100000); + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[56-IRQ_OFFSET]); + + dev = qdev_create(NULL, "generic-sdhci"); + qdev_init_nofail(dev); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0xE0101000); + sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic[79-IRQ_OFFSET]); + + dev = qdev_create(NULL, "pl330"); + qdev_prop_set_uint8(dev, "num_chnls", 8); + qdev_prop_set_uint8(dev, "num_periph_req", 4); + qdev_prop_set_uint8(dev, "num_events", 16); + + qdev_prop_set_uint8(dev, "data_width", 64); + qdev_prop_set_uint8(dev, "wr_cap", 8); + qdev_prop_set_uint8(dev, "wr_q_dep", 16); + qdev_prop_set_uint8(dev, "rd_cap", 8); + qdev_prop_set_uint8(dev, "rd_q_dep", 16); + qdev_prop_set_uint16(dev, "data_buffer_dep", 256); + + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(busdev, 0, 0xF8003000); + sysbus_connect_irq(busdev, 0, pic[45-IRQ_OFFSET]); /* abort irq line */ + for (n = 0; n < 8; ++n) { /* event irqs */ + sysbus_connect_irq(busdev, n + 1, pic[dma_irqs[n] - IRQ_OFFSET]); + } + + zynq_binfo.ram_size = ram_size; + zynq_binfo.kernel_filename = kernel_filename; + zynq_binfo.kernel_cmdline = kernel_cmdline; + zynq_binfo.initrd_filename = initrd_filename; + zynq_binfo.nb_cpus = 1; + zynq_binfo.board_id = 0xd32; + zynq_binfo.loader_start = 0; + arm_load_kernel(ARM_CPU(first_cpu), &zynq_binfo); +} + +static QEMUMachine zynq_machine = { + .name = "xilinx-zynq-a9", + .desc = "Xilinx Zynq Platform Baseboard for Cortex-A9", + .init = zynq_init, + .block_default_type = IF_SCSI, + .max_cpus = 1, + .no_sdcard = 1, +}; + +static void zynq_machine_init(void) +{ + qemu_register_machine(&zynq_machine); +} + +machine_init(zynq_machine_init); diff --git a/qemu/hw/arm/xlnx-ep108.c b/qemu/hw/arm/xlnx-ep108.c new file mode 100644 index 000000000..f94da86cb --- /dev/null +++ b/qemu/hw/arm/xlnx-ep108.c @@ -0,0 +1,82 @@ +/* + * Xilinx ZynqMP EP108 board + * + * Copyright (C) 2015 Xilinx Inc + * Written by Peter Crosthwaite <peter.crosthwaite@xilinx.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "hw/arm/xlnx-zynqmp.h" +#include "hw/boards.h" +#include "qemu/error-report.h" +#include "exec/address-spaces.h" + +typedef struct XlnxEP108 { + XlnxZynqMPState soc; + MemoryRegion ddr_ram; +} XlnxEP108; + +/* Max 2GB RAM */ +#define EP108_MAX_RAM_SIZE 0x80000000ull + +static struct arm_boot_info xlnx_ep108_binfo; + +static void xlnx_ep108_init(MachineState *machine) +{ + XlnxEP108 *s = g_new0(XlnxEP108, 1); + Error *err = NULL; + + object_initialize(&s->soc, sizeof(s->soc), TYPE_XLNX_ZYNQMP); + object_property_add_child(OBJECT(machine), "soc", OBJECT(&s->soc), + &error_abort); + + object_property_set_bool(OBJECT(&s->soc), true, "realized", &err); + if (err) { + error_report("%s", error_get_pretty(err)); + exit(1); + } + + if (machine->ram_size > EP108_MAX_RAM_SIZE) { + error_report("WARNING: RAM size " RAM_ADDR_FMT " above max supported, " + "reduced to %llx", machine->ram_size, EP108_MAX_RAM_SIZE); + machine->ram_size = EP108_MAX_RAM_SIZE; + } + + if (machine->ram_size <= 0x08000000) { + qemu_log("WARNING: RAM size " RAM_ADDR_FMT " is small for EP108", + machine->ram_size); + } + + memory_region_allocate_system_memory(&s->ddr_ram, NULL, "ddr-ram", + machine->ram_size); + memory_region_add_subregion(get_system_memory(), 0, &s->ddr_ram); + + xlnx_ep108_binfo.ram_size = machine->ram_size; + xlnx_ep108_binfo.kernel_filename = machine->kernel_filename; + xlnx_ep108_binfo.kernel_cmdline = machine->kernel_cmdline; + xlnx_ep108_binfo.initrd_filename = machine->initrd_filename; + xlnx_ep108_binfo.loader_start = 0; + arm_load_kernel(s->soc.boot_cpu_ptr, &xlnx_ep108_binfo); +} + +static QEMUMachine xlnx_ep108_machine = { + .name = "xlnx-ep108", + .desc = "Xilinx ZynqMP EP108 board", + .init = xlnx_ep108_init, +}; + +static void xlnx_ep108_machine_init(void) +{ + qemu_register_machine(&xlnx_ep108_machine); +} + +machine_init(xlnx_ep108_machine_init); diff --git a/qemu/hw/arm/xlnx-zynqmp.c b/qemu/hw/arm/xlnx-zynqmp.c new file mode 100644 index 000000000..62ef4ceb3 --- /dev/null +++ b/qemu/hw/arm/xlnx-zynqmp.c @@ -0,0 +1,266 @@ +/* + * Xilinx Zynq MPSoC emulation + * + * Copyright (C) 2015 Xilinx Inc + * Written by Peter Crosthwaite <peter.crosthwaite@xilinx.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "hw/arm/xlnx-zynqmp.h" +#include "hw/intc/arm_gic_common.h" +#include "exec/address-spaces.h" + +#define GIC_NUM_SPI_INTR 160 + +#define ARM_PHYS_TIMER_PPI 30 +#define ARM_VIRT_TIMER_PPI 27 + +#define GIC_BASE_ADDR 0xf9000000 +#define GIC_DIST_ADDR 0xf9010000 +#define GIC_CPU_ADDR 0xf9020000 + +static const uint64_t gem_addr[XLNX_ZYNQMP_NUM_GEMS] = { + 0xFF0B0000, 0xFF0C0000, 0xFF0D0000, 0xFF0E0000, +}; + +static const int gem_intr[XLNX_ZYNQMP_NUM_GEMS] = { + 57, 59, 61, 63, +}; + +static const uint64_t uart_addr[XLNX_ZYNQMP_NUM_UARTS] = { + 0xFF000000, 0xFF010000, +}; + +static const int uart_intr[XLNX_ZYNQMP_NUM_UARTS] = { + 21, 22, +}; + +typedef struct XlnxZynqMPGICRegion { + int region_index; + uint32_t address; +} XlnxZynqMPGICRegion; + +static const XlnxZynqMPGICRegion xlnx_zynqmp_gic_regions[] = { + { .region_index = 0, .address = GIC_DIST_ADDR, }, + { .region_index = 1, .address = GIC_CPU_ADDR, }, +}; + +static inline int arm_gic_ppi_index(int cpu_nr, int ppi_index) +{ + return GIC_NUM_SPI_INTR + cpu_nr * GIC_INTERNAL + ppi_index; +} + +static void xlnx_zynqmp_init(Object *obj) +{ + XlnxZynqMPState *s = XLNX_ZYNQMP(obj); + int i; + + for (i = 0; i < XLNX_ZYNQMP_NUM_APU_CPUS; i++) { + object_initialize(&s->apu_cpu[i], sizeof(s->apu_cpu[i]), + "cortex-a53-" TYPE_ARM_CPU); + object_property_add_child(obj, "apu-cpu[*]", OBJECT(&s->apu_cpu[i]), + &error_abort); + } + + for (i = 0; i < XLNX_ZYNQMP_NUM_RPU_CPUS; i++) { + object_initialize(&s->rpu_cpu[i], sizeof(s->rpu_cpu[i]), + "cortex-r5-" TYPE_ARM_CPU); + object_property_add_child(obj, "rpu-cpu[*]", OBJECT(&s->rpu_cpu[i]), + &error_abort); + } + + object_initialize(&s->gic, sizeof(s->gic), TYPE_ARM_GIC); + qdev_set_parent_bus(DEVICE(&s->gic), sysbus_get_default()); + + for (i = 0; i < XLNX_ZYNQMP_NUM_GEMS; i++) { + object_initialize(&s->gem[i], sizeof(s->gem[i]), TYPE_CADENCE_GEM); + qdev_set_parent_bus(DEVICE(&s->gem[i]), sysbus_get_default()); + } + + for (i = 0; i < XLNX_ZYNQMP_NUM_UARTS; i++) { + object_initialize(&s->uart[i], sizeof(s->uart[i]), TYPE_CADENCE_UART); + qdev_set_parent_bus(DEVICE(&s->uart[i]), sysbus_get_default()); + } +} + +static void xlnx_zynqmp_realize(DeviceState *dev, Error **errp) +{ + XlnxZynqMPState *s = XLNX_ZYNQMP(dev); + MemoryRegion *system_memory = get_system_memory(); + uint8_t i; + const char *boot_cpu = s->boot_cpu ? s->boot_cpu : "apu-cpu[0]"; + qemu_irq gic_spi[GIC_NUM_SPI_INTR]; + Error *err = NULL; + + qdev_prop_set_uint32(DEVICE(&s->gic), "num-irq", GIC_NUM_SPI_INTR + 32); + qdev_prop_set_uint32(DEVICE(&s->gic), "revision", 2); + qdev_prop_set_uint32(DEVICE(&s->gic), "num-cpu", XLNX_ZYNQMP_NUM_APU_CPUS); + object_property_set_bool(OBJECT(&s->gic), true, "realized", &err); + if (err) { + error_propagate((errp), (err)); + return; + } + assert(ARRAY_SIZE(xlnx_zynqmp_gic_regions) == XLNX_ZYNQMP_GIC_REGIONS); + for (i = 0; i < XLNX_ZYNQMP_GIC_REGIONS; i++) { + SysBusDevice *gic = SYS_BUS_DEVICE(&s->gic); + const XlnxZynqMPGICRegion *r = &xlnx_zynqmp_gic_regions[i]; + MemoryRegion *mr = sysbus_mmio_get_region(gic, r->region_index); + uint32_t addr = r->address; + int j; + + sysbus_mmio_map(gic, r->region_index, addr); + + for (j = 0; j < XLNX_ZYNQMP_GIC_ALIASES; j++) { + MemoryRegion *alias = &s->gic_mr[i][j]; + + addr += XLNX_ZYNQMP_GIC_REGION_SIZE; + memory_region_init_alias(alias, OBJECT(s), "zynqmp-gic-alias", mr, + 0, XLNX_ZYNQMP_GIC_REGION_SIZE); + memory_region_add_subregion(system_memory, addr, alias); + } + } + + for (i = 0; i < XLNX_ZYNQMP_NUM_APU_CPUS; i++) { + qemu_irq irq; + char *name; + + object_property_set_int(OBJECT(&s->apu_cpu[i]), QEMU_PSCI_CONDUIT_SMC, + "psci-conduit", &error_abort); + + name = object_get_canonical_path_component(OBJECT(&s->apu_cpu[i])); + if (strcmp(name, boot_cpu)) { + /* Secondary CPUs start in PSCI powered-down state */ + object_property_set_bool(OBJECT(&s->apu_cpu[i]), true, + "start-powered-off", &error_abort); + } else { + s->boot_cpu_ptr = &s->apu_cpu[i]; + } + g_free(name); + + object_property_set_int(OBJECT(&s->apu_cpu[i]), GIC_BASE_ADDR, + "reset-cbar", &err); + if (err) { + error_propagate((errp), (err)); + return; + } + + object_property_set_bool(OBJECT(&s->apu_cpu[i]), true, "realized", + &err); + if (err) { + error_propagate((errp), (err)); + return; + } + + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gic), i, + qdev_get_gpio_in(DEVICE(&s->apu_cpu[i]), + ARM_CPU_IRQ)); + irq = qdev_get_gpio_in(DEVICE(&s->gic), + arm_gic_ppi_index(i, ARM_PHYS_TIMER_PPI)); + qdev_connect_gpio_out(DEVICE(&s->apu_cpu[i]), 0, irq); + irq = qdev_get_gpio_in(DEVICE(&s->gic), + arm_gic_ppi_index(i, ARM_VIRT_TIMER_PPI)); + qdev_connect_gpio_out(DEVICE(&s->apu_cpu[i]), 1, irq); + } + + for (i = 0; i < XLNX_ZYNQMP_NUM_RPU_CPUS; i++) { + char *name; + + name = object_get_canonical_path_component(OBJECT(&s->rpu_cpu[i])); + if (strcmp(name, boot_cpu)) { + /* Secondary CPUs start in PSCI powered-down state */ + object_property_set_bool(OBJECT(&s->rpu_cpu[i]), true, + "start-powered-off", &error_abort); + } else { + s->boot_cpu_ptr = &s->rpu_cpu[i]; + } + g_free(name); + + object_property_set_bool(OBJECT(&s->rpu_cpu[i]), true, "reset-hivecs", + &err); + if (err != NULL) { + error_propagate(errp, err); + return; + } + + object_property_set_bool(OBJECT(&s->rpu_cpu[i]), true, "realized", + &err); + if (err) { + error_propagate((errp), (err)); + return; + } + } + + if (!s->boot_cpu_ptr) { + error_setg(errp, "ZynqMP Boot cpu %s not found\n", boot_cpu); + return; + } + + for (i = 0; i < GIC_NUM_SPI_INTR; i++) { + gic_spi[i] = qdev_get_gpio_in(DEVICE(&s->gic), i); + } + + for (i = 0; i < XLNX_ZYNQMP_NUM_GEMS; i++) { + NICInfo *nd = &nd_table[i]; + + if (nd->used) { + qemu_check_nic_model(nd, TYPE_CADENCE_GEM); + qdev_set_nic_properties(DEVICE(&s->gem[i]), nd); + } + object_property_set_bool(OBJECT(&s->gem[i]), true, "realized", &err); + if (err) { + error_propagate((errp), (err)); + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->gem[i]), 0, gem_addr[i]); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->gem[i]), 0, + gic_spi[gem_intr[i]]); + } + + for (i = 0; i < XLNX_ZYNQMP_NUM_UARTS; i++) { + object_property_set_bool(OBJECT(&s->uart[i]), true, "realized", &err); + if (err) { + error_propagate((errp), (err)); + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 0, uart_addr[i]); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0, + gic_spi[uart_intr[i]]); + } +} + +static Property xlnx_zynqmp_props[] = { + DEFINE_PROP_STRING("boot-cpu", XlnxZynqMPState, boot_cpu), + DEFINE_PROP_END_OF_LIST() +}; + +static void xlnx_zynqmp_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->props = xlnx_zynqmp_props; + dc->realize = xlnx_zynqmp_realize; +} + +static const TypeInfo xlnx_zynqmp_type_info = { + .name = TYPE_XLNX_ZYNQMP, + .parent = TYPE_DEVICE, + .instance_size = sizeof(XlnxZynqMPState), + .instance_init = xlnx_zynqmp_init, + .class_init = xlnx_zynqmp_class_init, +}; + +static void xlnx_zynqmp_register_types(void) +{ + type_register_static(&xlnx_zynqmp_type_info); +} + +type_init(xlnx_zynqmp_register_types) diff --git a/qemu/hw/arm/z2.c b/qemu/hw/arm/z2.c new file mode 100644 index 000000000..17355479a --- /dev/null +++ b/qemu/hw/arm/z2.c @@ -0,0 +1,386 @@ +/* + * PXA270-based Zipit Z2 device + * + * Copyright (c) 2011 by Vasily Khoruzhick <anarsoul@gmail.com> + * + * Code is based on mainstone platform. + * + * This code is licensed under the GNU GPL v2. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "hw/hw.h" +#include "hw/arm/pxa.h" +#include "hw/arm/arm.h" +#include "hw/devices.h" +#include "hw/i2c/i2c.h" +#include "hw/ssi.h" +#include "hw/boards.h" +#include "sysemu/sysemu.h" +#include "hw/block/flash.h" +#include "sysemu/block-backend.h" +#include "ui/console.h" +#include "audio/audio.h" +#include "exec/address-spaces.h" +#include "sysemu/qtest.h" + +#ifdef DEBUG_Z2 +#define DPRINTF(fmt, ...) \ + printf(fmt, ## __VA_ARGS__) +#else +#define DPRINTF(fmt, ...) +#endif + +static const struct keymap map[0x100] = { + [0 ... 0xff] = { -1, -1 }, + [0x3b] = {0, 0}, /* Option = F1 */ + [0xc8] = {0, 1}, /* Up */ + [0xd0] = {0, 2}, /* Down */ + [0xcb] = {0, 3}, /* Left */ + [0xcd] = {0, 4}, /* Right */ + [0xcf] = {0, 5}, /* End */ + [0x0d] = {0, 6}, /* KPPLUS */ + [0xc7] = {1, 0}, /* Home */ + [0x10] = {1, 1}, /* Q */ + [0x17] = {1, 2}, /* I */ + [0x22] = {1, 3}, /* G */ + [0x2d] = {1, 4}, /* X */ + [0x1c] = {1, 5}, /* Enter */ + [0x0c] = {1, 6}, /* KPMINUS */ + [0xc9] = {2, 0}, /* PageUp */ + [0x11] = {2, 1}, /* W */ + [0x18] = {2, 2}, /* O */ + [0x23] = {2, 3}, /* H */ + [0x2e] = {2, 4}, /* C */ + [0x38] = {2, 5}, /* LeftAlt */ + [0xd1] = {3, 0}, /* PageDown */ + [0x12] = {3, 1}, /* E */ + [0x19] = {3, 2}, /* P */ + [0x24] = {3, 3}, /* J */ + [0x2f] = {3, 4}, /* V */ + [0x2a] = {3, 5}, /* LeftShift */ + [0x01] = {4, 0}, /* Esc */ + [0x13] = {4, 1}, /* R */ + [0x1e] = {4, 2}, /* A */ + [0x25] = {4, 3}, /* K */ + [0x30] = {4, 4}, /* B */ + [0x1d] = {4, 5}, /* LeftCtrl */ + [0x0f] = {5, 0}, /* Tab */ + [0x14] = {5, 1}, /* T */ + [0x1f] = {5, 2}, /* S */ + [0x26] = {5, 3}, /* L */ + [0x31] = {5, 4}, /* N */ + [0x39] = {5, 5}, /* Space */ + [0x3c] = {6, 0}, /* Stop = F2 */ + [0x15] = {6, 1}, /* Y */ + [0x20] = {6, 2}, /* D */ + [0x0e] = {6, 3}, /* Backspace */ + [0x32] = {6, 4}, /* M */ + [0x33] = {6, 5}, /* Comma */ + [0x3d] = {7, 0}, /* Play = F3 */ + [0x16] = {7, 1}, /* U */ + [0x21] = {7, 2}, /* F */ + [0x2c] = {7, 3}, /* Z */ + [0x27] = {7, 4}, /* Semicolon */ + [0x34] = {7, 5}, /* Dot */ +}; + +#define Z2_RAM_SIZE 0x02000000 +#define Z2_FLASH_BASE 0x00000000 +#define Z2_FLASH_SIZE 0x00800000 + +static struct arm_boot_info z2_binfo = { + .loader_start = PXA2XX_SDRAM_BASE, + .ram_size = Z2_RAM_SIZE, +}; + +#define Z2_GPIO_SD_DETECT 96 +#define Z2_GPIO_AC_IN 0 +#define Z2_GPIO_KEY_ON 1 +#define Z2_GPIO_LCD_CS 88 + +typedef struct { + SSISlave ssidev; + int32_t selected; + int32_t enabled; + uint8_t buf[3]; + uint32_t cur_reg; + int pos; +} ZipitLCD; + +static uint32_t zipit_lcd_transfer(SSISlave *dev, uint32_t value) +{ + ZipitLCD *z = FROM_SSI_SLAVE(ZipitLCD, dev); + uint16_t val; + if (z->selected) { + z->buf[z->pos] = value & 0xff; + z->pos++; + } + if (z->pos == 3) { + switch (z->buf[0]) { + case 0x74: + DPRINTF("%s: reg: 0x%.2x\n", __func__, z->buf[2]); + z->cur_reg = z->buf[2]; + break; + case 0x76: + val = z->buf[1] << 8 | z->buf[2]; + DPRINTF("%s: value: 0x%.4x\n", __func__, val); + if (z->cur_reg == 0x22 && val == 0x0000) { + z->enabled = 1; + printf("%s: LCD enabled\n", __func__); + } else if (z->cur_reg == 0x10 && val == 0x0000) { + z->enabled = 0; + printf("%s: LCD disabled\n", __func__); + } + break; + default: + DPRINTF("%s: unknown command!\n", __func__); + break; + } + z->pos = 0; + } + return 0; +} + +static void z2_lcd_cs(void *opaque, int line, int level) +{ + ZipitLCD *z2_lcd = opaque; + z2_lcd->selected = !level; +} + +static int zipit_lcd_init(SSISlave *dev) +{ + ZipitLCD *z = FROM_SSI_SLAVE(ZipitLCD, dev); + z->selected = 0; + z->enabled = 0; + z->pos = 0; + + return 0; +} + +static VMStateDescription vmstate_zipit_lcd_state = { + .name = "zipit-lcd", + .version_id = 2, + .minimum_version_id = 2, + .fields = (VMStateField[]) { + VMSTATE_SSI_SLAVE(ssidev, ZipitLCD), + VMSTATE_INT32(selected, ZipitLCD), + VMSTATE_INT32(enabled, ZipitLCD), + VMSTATE_BUFFER(buf, ZipitLCD), + VMSTATE_UINT32(cur_reg, ZipitLCD), + VMSTATE_INT32(pos, ZipitLCD), + VMSTATE_END_OF_LIST(), + } +}; + +static void zipit_lcd_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SSISlaveClass *k = SSI_SLAVE_CLASS(klass); + + k->init = zipit_lcd_init; + k->transfer = zipit_lcd_transfer; + dc->vmsd = &vmstate_zipit_lcd_state; +} + +static const TypeInfo zipit_lcd_info = { + .name = "zipit-lcd", + .parent = TYPE_SSI_SLAVE, + .instance_size = sizeof(ZipitLCD), + .class_init = zipit_lcd_class_init, +}; + +#define TYPE_AER915 "aer915" +#define AER915(obj) OBJECT_CHECK(AER915State, (obj), TYPE_AER915) + +typedef struct AER915State { + I2CSlave parent_obj; + + int len; + uint8_t buf[3]; +} AER915State; + +static int aer915_send(I2CSlave *i2c, uint8_t data) +{ + AER915State *s = AER915(i2c); + + s->buf[s->len] = data; + if (s->len++ > 2) { + DPRINTF("%s: message too long (%i bytes)\n", + __func__, s->len); + return 1; + } + + if (s->len == 2) { + DPRINTF("%s: reg %d value 0x%02x\n", __func__, + s->buf[0], s->buf[1]); + } + + return 0; +} + +static void aer915_event(I2CSlave *i2c, enum i2c_event event) +{ + AER915State *s = AER915(i2c); + + switch (event) { + case I2C_START_SEND: + s->len = 0; + break; + case I2C_START_RECV: + if (s->len != 1) { + DPRINTF("%s: short message!?\n", __func__); + } + break; + case I2C_FINISH: + break; + default: + break; + } +} + +static int aer915_recv(I2CSlave *slave) +{ + AER915State *s = AER915(slave); + int retval = 0x00; + + switch (s->buf[0]) { + /* Return hardcoded battery voltage, + * 0xf0 means ~4.1V + */ + case 0x02: + retval = 0xf0; + break; + /* Return 0x00 for other regs, + * we don't know what they are for, + * anyway they return 0x00 on real hardware. + */ + default: + break; + } + + return retval; +} + +static int aer915_init(I2CSlave *i2c) +{ + /* Nothing to do. */ + return 0; +} + +static VMStateDescription vmstate_aer915_state = { + .name = "aer915", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_INT32(len, AER915State), + VMSTATE_BUFFER(buf, AER915State), + VMSTATE_END_OF_LIST(), + } +}; + +static void aer915_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + I2CSlaveClass *k = I2C_SLAVE_CLASS(klass); + + k->init = aer915_init; + k->event = aer915_event; + k->recv = aer915_recv; + k->send = aer915_send; + dc->vmsd = &vmstate_aer915_state; +} + +static const TypeInfo aer915_info = { + .name = TYPE_AER915, + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(AER915State), + .class_init = aer915_class_init, +}; + +static void z2_init(MachineState *machine) +{ + const char *cpu_model = machine->cpu_model; + const char *kernel_filename = machine->kernel_filename; + const char *kernel_cmdline = machine->kernel_cmdline; + const char *initrd_filename = machine->initrd_filename; + MemoryRegion *address_space_mem = get_system_memory(); + uint32_t sector_len = 0x10000; + PXA2xxState *mpu; + DriveInfo *dinfo; + int be; + void *z2_lcd; + I2CBus *bus; + DeviceState *wm; + + if (!cpu_model) { + cpu_model = "pxa270-c5"; + } + + /* Setup CPU & memory */ + mpu = pxa270_init(address_space_mem, z2_binfo.ram_size, cpu_model); + +#ifdef TARGET_WORDS_BIGENDIAN + be = 1; +#else + be = 0; +#endif + dinfo = drive_get(IF_PFLASH, 0, 0); + if (!dinfo && !qtest_enabled()) { + fprintf(stderr, "Flash image must be given with the " + "'pflash' parameter\n"); + exit(1); + } + + if (!pflash_cfi01_register(Z2_FLASH_BASE, + NULL, "z2.flash0", Z2_FLASH_SIZE, + dinfo ? blk_by_legacy_dinfo(dinfo) : NULL, + sector_len, Z2_FLASH_SIZE / sector_len, + 4, 0, 0, 0, 0, be)) { + fprintf(stderr, "qemu: Error registering flash memory.\n"); + exit(1); + } + + /* setup keypad */ + pxa27x_register_keypad(mpu->kp, map, 0x100); + + /* MMC/SD host */ + pxa2xx_mmci_handlers(mpu->mmc, + NULL, + qdev_get_gpio_in(mpu->gpio, Z2_GPIO_SD_DETECT)); + + type_register_static(&zipit_lcd_info); + type_register_static(&aer915_info); + z2_lcd = ssi_create_slave(mpu->ssp[1], "zipit-lcd"); + bus = pxa2xx_i2c_bus(mpu->i2c[0]); + i2c_create_slave(bus, TYPE_AER915, 0x55); + wm = i2c_create_slave(bus, "wm8750", 0x1b); + mpu->i2s->opaque = wm; + mpu->i2s->codec_out = wm8750_dac_dat; + mpu->i2s->codec_in = wm8750_adc_dat; + wm8750_data_req_set(wm, mpu->i2s->data_req, mpu->i2s); + + qdev_connect_gpio_out(mpu->gpio, Z2_GPIO_LCD_CS, + qemu_allocate_irq(z2_lcd_cs, z2_lcd, 0)); + + z2_binfo.kernel_filename = kernel_filename; + z2_binfo.kernel_cmdline = kernel_cmdline; + z2_binfo.initrd_filename = initrd_filename; + z2_binfo.board_id = 0x6dd; + arm_load_kernel(mpu->cpu, &z2_binfo); +} + +static QEMUMachine z2_machine = { + .name = "z2", + .desc = "Zipit Z2 (PXA27x)", + .init = z2_init, +}; + +static void z2_machine_init(void) +{ + qemu_register_machine(&z2_machine); +} + +machine_init(z2_machine_init); |