diff options
author | Anthony Liguori <aliguori@us.ibm.com> | 2013-03-10 19:39:07 -0500 |
---|---|---|
committer | Anthony Liguori <aliguori@us.ibm.com> | 2013-03-10 19:56:35 -0500 |
commit | 6e72a00f909dcd093fbdd1faa2b3c8caa1697a6c (patch) | |
tree | 8f275496c83f840e00daf4af97beac94b3a3e0b3 /hw/arm | |
parent | 6a245c666d02210b8461952eccc17379fdeedd9f (diff) | |
parent | 7a2771d1541ec9a0c585e9b853e5f4dc036919ad (diff) |
Merge remote-tracking branch 'bonzini/hw-dirs' into staging
* bonzini/hw-dirs:
sh: move files referencing CPU to hw/sh4/
ppc: move more files to hw/ppc
ppc: move files referencing CPU to hw/ppc/
m68k: move files referencing CPU to hw/m68k/
i386: move files referencing CPU to hw/i386/
arm: move files referencing CPU to hw/arm/
hw: move boards and other isolated files to hw/ARCH
ppc: express FDT dependency of pSeries and e500 boards via default-configs/
build: always link device_tree.o into emulators if libfdt available
hw: include hw header files with full paths
ppc: do not use ../ in include files
vt82c686: vt82c686 is not a PCI host bridge
virtio-9p: remove PCI dependencies from hw/9pfs/
virtio-9p: use CONFIG_VIRTFS, not CONFIG_LINUX
hw: move device-hotplug.o to toplevel, compile it once
hw: move qdev-monitor.o to toplevel directory
hw: move fifo.[ch] to libqemuutil
hw: move char backends to backends/
Conflicts:
backends/baum.c
backends/msmouse.c
hw/a15mpcore.c
hw/arm/Makefile.objs
hw/arm/pic_cpu.c
hw/dataplane/event-poll.c
hw/dataplane/virtio-blk.c
include/char/baum.h
include/char/msmouse.h
qemu-char.c
vl.c
Resolve conflicts caused by header movements.
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
Diffstat (limited to 'hw/arm')
-rw-r--r-- | hw/arm/Makefile.objs | 42 | ||||
-rw-r--r-- | hw/arm/armv7m.c | 284 | ||||
-rw-r--r-- | hw/arm/boot.c | 480 | ||||
-rw-r--r-- | hw/arm/collie.c | 73 | ||||
-rw-r--r-- | hw/arm/exynos4210.c | 349 | ||||
-rw-r--r-- | hw/arm/exynos4_boards.c | 170 | ||||
-rw-r--r-- | hw/arm/gumstix.c | 141 | ||||
-rw-r--r-- | hw/arm/highbank.c | 342 | ||||
-rw-r--r-- | hw/arm/integratorcp.c | 566 | ||||
-rw-r--r-- | hw/arm/kzm.c | 157 | ||||
-rw-r--r-- | hw/arm/mainstone.c | 190 | ||||
-rw-r--r-- | hw/arm/musicpal.c | 1697 | ||||
-rw-r--r-- | hw/arm/nseries.c | 1430 | ||||
-rw-r--r-- | hw/arm/omap1.c | 4056 | ||||
-rw-r--r-- | hw/arm/omap2.c | 2684 | ||||
-rw-r--r-- | hw/arm/omap_sx1.c | 238 | ||||
-rw-r--r-- | hw/arm/palm.c | 291 | ||||
-rw-r--r-- | hw/arm/pic_cpu.c | 66 | ||||
-rw-r--r-- | hw/arm/pxa2xx.c | 2291 | ||||
-rw-r--r-- | hw/arm/pxa2xx_gpio.c | 350 | ||||
-rw-r--r-- | hw/arm/pxa2xx_pic.c | 334 | ||||
-rw-r--r-- | hw/arm/realview.c | 404 | ||||
-rw-r--r-- | hw/arm/spitz.c | 1138 | ||||
-rw-r--r-- | hw/arm/stellaris.c | 1401 | ||||
-rw-r--r-- | hw/arm/tosa.c | 302 | ||||
-rw-r--r-- | hw/arm/versatilepb.c | 403 | ||||
-rw-r--r-- | hw/arm/vexpress.c | 500 | ||||
-rw-r--r-- | hw/arm/xilinx_zynq.c | 224 | ||||
-rw-r--r-- | hw/arm/z2.c | 384 |
29 files changed, 20966 insertions, 21 deletions
diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs index 0e7df6098a..2d9c69dfce 100644 --- a/hw/arm/Makefile.objs +++ b/hw/arm/Makefile.objs @@ -1,37 +1,37 @@ -obj-y = integratorcp.o versatilepb.o arm_pic.o -obj-y += arm_boot.o -obj-y += xilinx_zynq.o zynq_slcr.o +obj-y += zynq_slcr.o obj-y += xilinx_spips.o obj-y += arm_gic.o arm_gic_common.o obj-y += a9scu.o -obj-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o -obj-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o -obj-y += exynos4_boards.o exynos4210_uart.o exynos4210_pwm.o +obj-y += realview_gic.o arm_sysctl.o arm11mpcore.o a9mpcore.o +obj-y += exynos4210_gic.o exynos4210_combiner.o +obj-y += exynos4210_uart.o exynos4210_pwm.o obj-y += exynos4210_pmu.o exynos4210_mct.o exynos4210_fimd.o obj-y += exynos4210_rtc.o exynos4210_i2c.o obj-y += arm_mptimer.o a15mpcore.o -obj-y += armv7m.o armv7m_nvic.o stellaris.o stellaris_enet.o -obj-y += highbank.o -obj-y += pxa2xx.o pxa2xx_pic.o pxa2xx_gpio.o pxa2xx_timer.o pxa2xx_dma.o +obj-y += armv7m_nvic.o stellaris_enet.o +obj-y += pxa2xx_timer.o pxa2xx_dma.o obj-y += pxa2xx_lcd.o pxa2xx_mmci.o pxa2xx_pcmcia.o pxa2xx_keypad.o -obj-y += gumstix.o -obj-y += zaurus.o ide/microdrive.o spitz.o tosa.o tc6393xb.o -obj-y += omap1.o omap_lcdc.o omap_dma.o omap_clk.o omap_mmc.o omap_i2c.o \ +obj-y += zaurus.o ide/microdrive.o tc6393xb.o +obj-y += omap_lcdc.o omap_dma.o omap_clk.o omap_mmc.o omap_i2c.o \ omap_gpio.o omap_intc.o omap_uart.o -obj-y += omap2.o omap_dss.o soc_dma.o omap_gptimer.o omap_synctimer.o \ +obj-y += omap_dss.o soc_dma.o omap_gptimer.o omap_synctimer.o \ omap_gpmc.o omap_sdrc.o omap_spi.o omap_tap.o omap_l4.o -obj-y += omap_sx1.o palm.o tsc210x.o -obj-y += nseries.o blizzard.o onenand.o cbus.o tusb6010.o usb/hcd-musb.o -obj-y += mst_fpga.o mainstone.o -obj-y += z2.o -obj-y += musicpal.o bitbang_i2c.o marvell_88w8618_audio.o +obj-y += tsc210x.o +obj-y += blizzard.o onenand.o cbus.o tusb6010.o usb/hcd-musb.o +obj-y += mst_fpga.o +obj-y += bitbang_i2c.o marvell_88w8618_audio.o obj-y += framebuffer.o -obj-y += vexpress.o obj-y += strongarm.o -obj-y += collie.o obj-y += imx_serial.o imx_ccm.o imx_timer.o imx_avic.o -obj-y += kzm.o obj-$(CONFIG_FDT) += ../device_tree.o obj-$(CONFIG_KVM) += kvm/arm_gic.o obj-y := $(addprefix ../,$(obj-y)) + +obj-y += boot.o collie.o exynos4_boards.o gumstix.o highbank.o +obj-y += integratorcp.o kzm.o mainstone.o musicpal.o nseries.o +obj-y += omap_sx1.o palm.o pic_cpu.o realview.o spitz.o stellaris.o +obj-y += tosa.o versatilepb.o vexpress.o xilinx_zynq.o z2.o + +obj-y += armv7m.o exynos4210.o pxa2xx.o pxa2xx_gpio.o pxa2xx_pic.o +obj-y += omap1.o omap2.o diff --git a/hw/arm/armv7m.c b/hw/arm/armv7m.c new file mode 100644 index 0000000000..1d5bb592c4 --- /dev/null +++ b/hw/arm/armv7m.c @@ -0,0 +1,284 @@ +/* + * 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-misc.h" +#include "hw/loader.h" +#include "elf.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, (uint8_t *)&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, (uint8_t *)&v, 2); + if (value & 1) + v |= mask; + else + v &= ~mask; + cpu_physical_memory_write(addr, (uint8_t *)&v, 2); +} + +static uint32_t bitband_readl(void *opaque, 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, (uint8_t *)&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, (uint8_t *)&v, 4); + if (value & 1) + v |= mask; + else + v &= ~mask; + cpu_physical_memory_write(addr, (uint8_t *)&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, +}; + +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + uint32_t base; +} BitBandState; + +static int bitband_init(SysBusDevice *dev) +{ + BitBandState *s = FROM_SYSBUS(BitBandState, dev); + + memory_region_init_io(&s->iomem, &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, "ARM,bitband-memory"); + qdev_prop_set_uint32(dev, "base", 0x20000000); + qdev_init_nofail(dev); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, 0x22000000); + + dev = qdev_create(NULL, "ARM,bitband-memory"); + 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. + flash_size and sram_size are in kb. + Returns the NVIC array. */ + +qemu_irq *armv7m_init(MemoryRegion *address_space_mem, + int flash_size, int sram_size, + const char *kernel_filename, const char *cpu_model) +{ + ARMCPU *cpu; + CPUARMState *env; + DeviceState *nvic; + /* FIXME: make this local state. */ + static qemu_irq pic[64]; + qemu_irq *cpu_pic; + int image_size; + uint64_t entry; + uint64_t lowaddr; + int i; + int big_endian; + MemoryRegion *sram = g_new(MemoryRegion, 1); + MemoryRegion *flash = g_new(MemoryRegion, 1); + MemoryRegion *hack = g_new(MemoryRegion, 1); + + flash_size *= 1024; + sram_size *= 1024; + + 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; + +#if 0 + /* > 32Mb SRAM gets complicated because it overlaps the bitband area. + We don't have proper commandline options, so allocate half of memory + as SRAM, up to a maximum of 32Mb, and the rest as code. */ + if (ram_size > (512 + 32) * 1024 * 1024) + ram_size = (512 + 32) * 1024 * 1024; + sram_size = (ram_size / 2) & TARGET_PAGE_MASK; + if (sram_size > 32 * 1024 * 1024) + sram_size = 32 * 1024 * 1024; + code_size = ram_size - sram_size; +#endif + + /* Flash programming is done via the SCU, so pretend it is ROM. */ + memory_region_init_ram(flash, "armv7m.flash", flash_size); + vmstate_register_ram_global(flash); + memory_region_set_readonly(flash, true); + memory_region_add_subregion(address_space_mem, 0, flash); + memory_region_init_ram(sram, "armv7m.sram", sram_size); + vmstate_register_ram_global(sram); + memory_region_add_subregion(address_space_mem, 0x20000000, sram); + armv7m_bitband_init(); + + nvic = qdev_create(NULL, "armv7m_nvic"); + env->nvic = nvic; + qdev_init_nofail(nvic); + cpu_pic = arm_pic_init_cpu(cpu); + sysbus_connect_irq(SYS_BUS_DEVICE(nvic), 0, cpu_pic[ARM_PIC_CPU_IRQ]); + for (i = 0; i < 64; i++) { + pic[i] = qdev_get_gpio_in(nvic, i); + } + +#ifdef TARGET_WORDS_BIGENDIAN + big_endian = 1; +#else + big_endian = 0; +#endif + + if (!kernel_filename) { + fprintf(stderr, "Guest image must be specified (using -kernel)\n"); + exit(1); + } + + 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, flash_size); + lowaddr = 0; + } + if (image_size < 0) { + fprintf(stderr, "qemu: could not load kernel '%s'\n", + 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, "armv7m.hack", 0x1000); + vmstate_register_ram_global(hack); + memory_region_add_subregion(address_space_mem, 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 = "ARM,bitband-memory", + .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/hw/arm/boot.c b/hw/arm/boot.c new file mode 100644 index 0000000000..43253fd34a --- /dev/null +++ b/hw/arm/boot.c @@ -0,0 +1,480 @@ +/* + * 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-misc.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" + +#define KERNEL_ARGS_ADDR 0x100 +#define KERNEL_LOAD_ADDR 0x00010000 + +/* The worlds second smallest bootloader. Set r0-r2, then jump to kernel. */ +static uint32_t bootloader[] = { + 0xe3a00000, /* mov r0, #0 */ + 0xe59f1004, /* ldr r1, [pc, #4] */ + 0xe59f2004, /* ldr r2, [pc, #4] */ + 0xe59ff004, /* ldr pc, [pc, #4] */ + 0, /* Board ID */ + 0, /* Address of kernel args. Set by integratorcp_init. */ + 0 /* Kernel entry point. Set by integratorcp_init. */ +}; + +/* 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 uint32_t smpboot[] = { + 0xe59f2028, /* ldr r2, gic_cpu_if */ + 0xe59f0028, /* ldr r0, startaddr */ + 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 */ + DSB_INSN, /* dsb */ + 0xe320f003, /* wfi */ + 0xe5901000, /* ldr r1, [r0] */ + 0xe1110001, /* tst r1, r1 */ + 0x0afffffb, /* beq <wfi> */ + 0xe12fff11, /* bx r1 */ + 0, /* gic_cpu_if: base address of GIC CPU interface */ + 0 /* bootreg: Boot register address is held here */ +}; + +static void default_write_secondary(ARMCPU *cpu, + const struct arm_boot_info *info) +{ + int n; + 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++) { + /* Replace DSB with the pre-v7 DSB if necessary. */ + if (!arm_feature(&cpu->env, ARM_FEATURE_V7) && + smpboot[n] == DSB_INSN) { + smpboot[n] = CP15_DSB_INSN; + } + smpboot[n] = tswap32(smpboot[n]); + } + rom_add_blob_fixed("smpboot", smpboot, sizeof(smpboot), + info->smp_loader_start); +} + +static void default_reset_secondary(ARMCPU *cpu, + const struct arm_boot_info *info) +{ + CPUARMState *env = &cpu->env; + + stl_phys_notdirty(info->smp_bootreg_addr, 0); + env->regs[15] = info->smp_loader_start; +} + +#define WRITE_WORD(p, value) do { \ + stl_phys_notdirty(p, value); \ + 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, (void *)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, (void *)s, strlen(s) + 1); + } else { + WRITE_WORD(p, 0); + } +} + +static int load_dtb(hwaddr addr, const struct arm_boot_info *binfo) +{ +#ifdef CONFIG_FDT + uint32_t *mem_reg_property; + uint32_t mem_reg_propsize; + void *fdt = NULL; + char *filename; + int size, rc; + uint32_t acells, scells, hival; + + 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); + return -1; + } + + fdt = load_device_tree(filename, &size); + if (!fdt) { + fprintf(stderr, "Couldn't open dtb file %s\n", filename); + g_free(filename); + return -1; + } + g_free(filename); + + acells = qemu_devtree_getprop_cell(fdt, "/", "#address-cells"); + scells = qemu_devtree_getprop_cell(fdt, "/", "#size-cells"); + if (acells == 0 || scells == 0) { + fprintf(stderr, "dtb file invalid (#address-cells or #size-cells 0)\n"); + return -1; + } + + mem_reg_propsize = acells + scells; + mem_reg_property = g_new0(uint32_t, mem_reg_propsize); + mem_reg_property[acells - 1] = cpu_to_be32(binfo->loader_start); + hival = cpu_to_be32(binfo->loader_start >> 32); + if (acells > 1) { + mem_reg_property[acells - 2] = hival; + } else if (hival != 0) { + fprintf(stderr, "qemu: dtb file not compatible with " + "RAM start address > 4GB\n"); + exit(1); + } + mem_reg_property[acells + scells - 1] = cpu_to_be32(binfo->ram_size); + hival = cpu_to_be32(binfo->ram_size >> 32); + if (scells > 1) { + mem_reg_property[acells + scells - 2] = hival; + } else if (hival != 0) { + fprintf(stderr, "qemu: dtb file not compatible with " + "RAM size > 4GB\n"); + exit(1); + } + + rc = qemu_devtree_setprop(fdt, "/memory", "reg", mem_reg_property, + mem_reg_propsize * sizeof(uint32_t)); + if (rc < 0) { + fprintf(stderr, "couldn't set /memory/reg\n"); + } + + if (binfo->kernel_cmdline && *binfo->kernel_cmdline) { + rc = qemu_devtree_setprop_string(fdt, "/chosen", "bootargs", + binfo->kernel_cmdline); + if (rc < 0) { + fprintf(stderr, "couldn't set /chosen/bootargs\n"); + } + } + + if (binfo->initrd_size) { + rc = qemu_devtree_setprop_cell(fdt, "/chosen", "linux,initrd-start", + binfo->initrd_start); + if (rc < 0) { + fprintf(stderr, "couldn't set /chosen/linux,initrd-start\n"); + } + + rc = qemu_devtree_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"); + } + } + + cpu_physical_memory_write(addr, fdt, size); + + return 0; + +#else + fprintf(stderr, "Device tree requested, " + "but qemu was compiled without fdt support\n"); + return -1; +#endif +} + +static void do_cpu_reset(void *opaque) +{ + ARMCPU *cpu = opaque; + CPUARMState *env = &cpu->env; + const struct arm_boot_info *info = env->boot_info; + + cpu_reset(CPU(cpu)); + if (info) { + if (!info->is_linux) { + /* Jump to the entry point. */ + env->regs[15] = info->entry & 0xfffffffe; + env->thumb = info->entry & 1; + } else { + if (env == first_cpu) { + env->regs[15] = info->loader_start; + if (!info->dtb_filename) { + if (old_param) { + set_kernel_args_old(info); + } else { + set_kernel_args(info); + } + } + } else { + info->secondary_cpu_reset_hook(cpu, info); + } + } + } +} + +void arm_load_kernel(ARMCPU *cpu, struct arm_boot_info *info) +{ + CPUARMState *env = &cpu->env; + int kernel_size; + int initrd_size; + int n; + int is_linux = 0; + uint64_t elf_entry; + hwaddr entry; + int big_endian; + QemuOpts *machine_opts; + + /* Load the kernel. */ + if (!info->kernel_filename) { + fprintf(stderr, "Kernel image must be specified\n"); + exit(1); + } + + machine_opts = qemu_opts_find(qemu_find_opts("machine"), 0); + if (machine_opts) { + info->dtb_filename = qemu_opt_get(machine_opts, "dtb"); + } else { + info->dtb_filename = NULL; + } + + 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, + NULL, NULL, big_endian, ELF_MACHINE, 1); + entry = elf_entry; + if (kernel_size < 0) { + kernel_size = load_uimage(info->kernel_filename, &entry, NULL, + &is_linux); + } + if (kernel_size < 0) { + entry = info->loader_start + KERNEL_LOAD_ADDR; + kernel_size = load_image_targphys(info->kernel_filename, entry, + info->ram_size - KERNEL_LOAD_ADDR); + 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) { + if (info->initrd_filename) { + 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; + + bootloader[4] = info->board_id; + + /* for device tree boot, we pass the DTB directly in r2. Otherwise + * we point to the kernel args. + */ + if (info->dtb_filename) { + /* Place the DTB after the initrd in memory. Note that some + * kernels will trash anything in the 4K page the initrd + * ends in, so make sure the DTB isn't caught up in that. + */ + hwaddr dtb_start = QEMU_ALIGN_UP(info->initrd_start + initrd_size, + 4096); + if (load_dtb(dtb_start, info)) { + exit(1); + } + bootloader[5] = dtb_start; + } else { + bootloader[5] = 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); + } + } + bootloader[6] = entry; + for (n = 0; n < sizeof(bootloader) / 4; n++) { + bootloader[n] = tswap32(bootloader[n]); + } + rom_add_blob_fixed("bootloader", bootloader, sizeof(bootloader), + info->loader_start); + if (info->nb_cpus > 1) { + info->write_secondary_boot(cpu, info); + } + } + info->is_linux = is_linux; + + for (; env; env = env->next_cpu) { + cpu = arm_env_get_cpu(env); + env->boot_info = info; + qemu_register_reset(do_cpu_reset, cpu); + } +} diff --git a/hw/arm/collie.c b/hw/arm/collie.c new file mode 100644 index 0000000000..17fddc8d5b --- /dev/null +++ b/hw/arm/collie.c @@ -0,0 +1,73 @@ +/* + * 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 "hw/strongarm.h" +#include "hw/arm-misc.h" +#include "hw/flash.h" +#include "sysemu/blockdev.h" +#include "exec/address-spaces.h" + +static struct arm_boot_info collie_binfo = { + .loader_start = SA_SDCS0, + .ram_size = 0x20000000, +}; + +static void collie_init(QEMUMachineInitArgs *args) +{ + const char *cpu_model = args->cpu_model; + const char *kernel_filename = args->kernel_filename; + const char *kernel_cmdline = args->kernel_cmdline; + const char *initrd_filename = args->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 ? dinfo->bdrv : 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 ? dinfo->bdrv : 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, + DEFAULT_MACHINE_OPTIONS, +}; + +static void collie_machine_init(void) +{ + qemu_register_machine(&collie_machine); +} + +machine_init(collie_machine_init) diff --git a/hw/arm/exynos4210.c b/hw/arm/exynos4210.c new file mode 100644 index 0000000000..4592514bb2 --- /dev/null +++ b/hw/arm/exynos4210.c @@ -0,0 +1,349 @@ +/* + * 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-misc.h" +#include "hw/loader.h" +#include "hw/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 }; + +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) +{ + qemu_irq cpu_irq[EXYNOS4210_NCPUS]; + int i, n; + Exynos4210State *s = g_new(Exynos4210State, 1); + qemu_irq *irqp; + qemu_irq gate_irq[EXYNOS4210_NCPUS][EXYNOS4210_IRQ_GATE_NINPUTS]; + unsigned long mem_size; + DeviceState *dev; + SysBusDevice *busdev; + + for (n = 0; n < EXYNOS4210_NCPUS; n++) { + s->cpu[n] = cpu_arm_init("cortex-a9"); + if (!s->cpu[n]) { + fprintf(stderr, "Unable to find CPU %d definition\n", n); + exit(1); + } + + /* Create PIC controller for each processor instance */ + irqp = arm_pic_init_cpu(s->cpu[n]); + + /* + * Get GICs gpio_in cpu_irq to connect a combiner to them later. + * Use only IRQ for a while. + */ + cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ]; + } + + /*** 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_irq */ + sysbus_connect_irq(busdev, 0, cpu_irq[i]); + } + + /* 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_ram_ptr(&s->chipid_mem, "exynos4210.chipid", + sizeof(chipid_and_omr), chipid_and_omr); + memory_region_set_readonly(&s->chipid_mem, true); + memory_region_add_subregion(system_mem, EXYNOS4210_CHIPID_ADDR, + &s->chipid_mem); + + /* Internal ROM */ + memory_region_init_ram(&s->irom_mem, "exynos4210.irom", + EXYNOS4210_IROM_SIZE); + 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, "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, "exynos4210.iram", + EXYNOS4210_IRAM_SIZE); + 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, "exynos4210.dram1", + mem_size - EXYNOS4210_DRAM_MAX_SIZE); + 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, "exynos4210.dram0", mem_size); + 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] = (i2c_bus *)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/hw/arm/exynos4_boards.c b/hw/arm/exynos4_boards.c new file mode 100644 index 0000000000..473da349bd --- /dev/null +++ b/hw/arm/exynos4_boards.c @@ -0,0 +1,170 @@ +/* + * 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 "hw/sysbus.h" +#include "net/net.h" +#include "hw/arm-misc.h" +#include "exec/address-spaces.h" +#include "hw/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(QEMUMachineInitArgs *args, + Exynos4BoardType board_type) +{ + if (smp_cpus != EXYNOS4210_NCPUS) { + 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 = args->kernel_filename; + exynos4_board_binfo.initrd_filename = args->initrd_filename; + exynos4_board_binfo.kernel_cmdline = args->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], + args->kernel_filename, + args->kernel_cmdline, + args->initrd_filename); + + return exynos4210_init(get_system_memory(), + exynos4_board_ram_size[board_type]); +} + +static void nuri_init(QEMUMachineInitArgs *args) +{ + exynos4_boards_init_common(args, EXYNOS4_BOARD_NURI); + + arm_load_kernel(arm_env_get_cpu(first_cpu), &exynos4_board_binfo); +} + +static void smdkc210_init(QEMUMachineInitArgs *args) +{ + Exynos4210State *s = exynos4_boards_init_common(args, + EXYNOS4_BOARD_SMDKC210); + + lan9215_init(SMDK_LAN9118_BASE_ADDR, + qemu_irq_invert(s->irq_table[exynos4210_get_irq(37, 1)])); + arm_load_kernel(arm_env_get_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, + DEFAULT_MACHINE_OPTIONS, + }, + [EXYNOS4_BOARD_SMDKC210] = { + .name = "smdkc210", + .desc = "Samsung SMDKC210 board (Exynos4210)", + .init = smdkc210_init, + .max_cpus = EXYNOS4210_NCPUS, + DEFAULT_MACHINE_OPTIONS, + }, +}; + +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/hw/arm/gumstix.c b/hw/arm/gumstix.c new file mode 100644 index 0000000000..8859b7392f --- /dev/null +++ b/hw/arm/gumstix.c @@ -0,0 +1,141 @@ +/* + * 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/pxa.h" +#include "net/net.h" +#include "hw/flash.h" +#include "hw/devices.h" +#include "hw/boards.h" +#include "sysemu/blockdev.h" +#include "exec/address-spaces.h" + +static const int sector_len = 128 * 1024; + +static void connex_init(QEMUMachineInitArgs *args) +{ + 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) { + 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->bdrv, 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(QEMUMachineInitArgs *args) +{ + const char *cpu_model = args->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) { + 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->bdrv, 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, + DEFAULT_MACHINE_OPTIONS, +}; + +static QEMUMachine verdex_machine = { + .name = "verdex", + .desc = "Gumstix Verdex (PXA270)", + .init = verdex_init, + DEFAULT_MACHINE_OPTIONS, +}; + +static void gumstix_machine_init(void) +{ + qemu_register_machine(&connex_machine); + qemu_register_machine(&verdex_machine); +} + +machine_init(gumstix_machine_init); diff --git a/hw/arm/highbank.c b/hw/arm/highbank.c new file mode 100644 index 0000000000..a622224dcc --- /dev/null +++ b/hw/arm/highbank.c @@ -0,0 +1,342 @@ +/* + * 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-misc.h" +#include "hw/devices.h" +#include "hw/loader.h" +#include "net/net.h" +#include "sysemu/sysemu.h" +#include "hw/boards.h" +#include "hw/sysbus.h" +#include "sysemu/blockdev.h" +#include "exec/address-spaces.h" + +#define SMP_BOOT_ADDR 0x100 +#define SMP_BOOT_REG 0x40 +#define GIC_BASE_ADDR 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 */ + GIC_BASE_ADDR /* privbase: gic 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: + stl_phys_notdirty(SMP_BOOT_REG + 0x30, 0); + case 3: + stl_phys_notdirty(SMP_BOOT_REG + 0x20, 0); + case 2: + stl_phys_notdirty(SMP_BOOT_REG + 0x10, 0); + 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, +}; + +typedef struct { + SysBusDevice busdev; + MemoryRegion *iomem; + uint32_t regs[NUM_REGS]; +} HighbankRegsState; + +static VMStateDescription vmstate_highbank_regs = { + .name = "highbank-regs", + .version_id = 0, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, HighbankRegsState, NUM_REGS), + VMSTATE_END_OF_LIST(), + }, +}; + +static void highbank_regs_reset(DeviceState *dev) +{ + SysBusDevice *sys_dev = SYS_BUS_DEVICE(dev); + HighbankRegsState *s = FROM_SYSBUS(HighbankRegsState, sys_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 = FROM_SYSBUS(HighbankRegsState, dev); + + s->iomem = g_new(MemoryRegion, 1); + memory_region_init_io(s->iomem, &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 = "highbank-regs", + .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; + +/* 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 highbank_init(QEMUMachineInitArgs *args) +{ + ram_addr_t ram_size = args->ram_size; + const char *cpu_model = args->cpu_model; + const char *kernel_filename = args->kernel_filename; + const char *kernel_cmdline = args->kernel_cmdline; + const char *initrd_filename = args->initrd_filename; + DeviceState *dev; + SysBusDevice *busdev; + qemu_irq *irqp; + qemu_irq pic[128]; + int n; + qemu_irq cpu_irq[4]; + MemoryRegion *sysram; + MemoryRegion *dram; + MemoryRegion *sysmem; + char *sysboot_filename; + + if (!cpu_model) { + cpu_model = "cortex-a9"; + } + + for (n = 0; n < smp_cpus; n++) { + ARMCPU *cpu; + cpu = cpu_arm_init(cpu_model); + if (cpu == NULL) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + + /* This will become a QOM property eventually */ + cpu->reset_cbar = GIC_BASE_ADDR; + irqp = arm_pic_init_cpu(cpu); + cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ]; + } + + sysmem = get_system_memory(); + dram = g_new(MemoryRegion, 1); + memory_region_init_ram(dram, "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, "highbank.sysram", 0x8000); + 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) { + uint32_t filesize = get_image_size(sysboot_filename); + if (load_image_targphys("sysram.bin", 0xfff88000, filesize) < 0) { + hw_error("Unable to load %s\n", bios_name); + } + } else { + hw_error("Unable to find %s\n", bios_name); + } + } + + dev = qdev_create(NULL, "a9mpcore_priv"); + 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, GIC_BASE_ADDR); + for (n = 0; n < smp_cpus; n++) { + sysbus_connect_irq(busdev, n, cpu_irq[n]); + } + + for (n = 0; n < 128; n++) { + pic[n] = qdev_get_gpio_in(dev, n); + } + + dev = qdev_create(NULL, "l2x0"); + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(busdev, 0, 0xfff12000); + + 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_env_get_cpu(first_cpu), &highbank_binfo); +} + +static QEMUMachine highbank_machine = { + .name = "highbank", + .desc = "Calxeda Highbank (ECX-1000)", + .init = highbank_init, + .block_default_type = IF_SCSI, + .max_cpus = 4, + DEFAULT_MACHINE_OPTIONS, +}; + +static void highbank_machine_init(void) +{ + qemu_register_machine(&highbank_machine); +} + +machine_init(highbank_machine_init); diff --git a/hw/arm/integratorcp.c b/hw/arm/integratorcp.c new file mode 100644 index 0000000000..e0ba327a55 --- /dev/null +++ b/hw/arm/integratorcp.c @@ -0,0 +1,566 @@ +/* + * 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-misc.h" +#include "net/net.h" +#include "exec/address-spaces.h" +#include "sysemu/sysemu.h" + +typedef struct { + SysBusDevice busdev; + 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 int_level; + uint32_t irq_enabled; + uint32_t fiq_enabled; +} integratorcm_state; + +static uint8_t integrator_spd[128] = { + 128, 8, 4, 11, 9, 1, 64, 0, 2, 0xa0, 0xa0, 0, 0, 8, 0, 1, + 0xe, 4, 0x1c, 1, 2, 0x20, 0xc0, 0, 0, 0, 0, 0x30, 0x28, 0x30, 0x28, 0x40 +}; + +static uint64_t integratorcm_read(void *opaque, hwaddr offset, + unsigned size) +{ + integratorcm_state *s = (integratorcm_state *)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_REFCT */ + /* ??? High frequency timer. */ + hw_error("integratorcm_read: CM_REFCT"); + case 12: /* CM_FLAGS */ + return s->cm_flags; + case 14: /* CM_NVFLAGS */ + return s->cm_nvflags; + case 16: /* CM_IRQ_STAT */ + return s->int_level & s->irq_enabled; + case 17: /* CM_IRQ_RSTAT */ + return s->int_level; + case 18: /* CM_IRQ_ENSET */ + return s->irq_enabled; + case 20: /* CM_SOFT_INTSET */ + return s->int_level & 1; + case 24: /* CM_FIQ_STAT */ + return s->int_level & s->fiq_enabled; + case 25: /* CM_FIQ_RSTAT */ + return s->int_level; + case 26: /* CM_FIQ_ENSET */ + return s->fiq_enabled; + case 32: /* CM_VOLTAGE_CTL0 */ + case 33: /* CM_VOLTAGE_CTL1 */ + case 34: /* CM_VOLTAGE_CTL2 */ + case 35: /* CM_VOLTAGE_CTL3 */ + /* ??? Voltage control unimplemented. */ + return 0; + default: + hw_error("integratorcm_read: Unimplemented offset 0x%x\n", + (int)offset); + return 0; + } +} + +static void integratorcm_do_remap(integratorcm_state *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(integratorcm_state *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(integratorcm_state *s) +{ + /* ??? The CPU irq/fiq is raised when either the core module or base PIC + are active. */ + if (s->int_level & (s->irq_enabled | s->fiq_enabled)) + hw_error("Core module interrupt\n"); +} + +static void integratorcm_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + integratorcm_state *s = (integratorcm_state *)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) +{ + integratorcm_state *s = FROM_SYSBUS(integratorcm_state, 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; + memory_region_init_ram(&s->flash, "integrator.flash", 0x100000); + vmstate_register_ram_global(&s->flash); + + memory_region_init_io(&s->iomem, &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. */ + +typedef struct icp_pic_state +{ + SysBusDevice busdev; + 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 *dev) +{ + icp_pic_state *s = FROM_SYSBUS(icp_pic_state, dev); + + qdev_init_gpio_in(&dev->qdev, icp_pic_set_irq, 32); + sysbus_init_irq(dev, &s->parent_irq); + sysbus_init_irq(dev, &s->parent_fiq); + memory_region_init_io(&s->iomem, &icp_pic_ops, s, "icp-pic", 0x00800000); + sysbus_init_mmio(dev, &s->iomem); + return 0; +} + +/* CP control registers. */ + +static uint64_t icp_control_read(void *opaque, hwaddr offset, + unsigned size) +{ + switch (offset >> 2) { + case 0: /* CP_IDFIELD */ + return 0x41034003; + case 1: /* CP_FLASHPROG */ + return 0; + case 2: /* CP_INTREG */ + return 0; + case 3: /* CP_DECODE */ + return 0x11; + default: + 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) +{ + switch (offset >> 2) { + case 1: /* CP_FLASHPROG */ + case 2: /* CP_INTREG */ + 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_init(hwaddr base) +{ + MemoryRegion *io; + + io = (MemoryRegion *)g_malloc0(sizeof(MemoryRegion)); + memory_region_init_io(io, &icp_control_ops, NULL, + "control", 0x00800000); + memory_region_add_subregion(get_system_memory(), base, io); + /* ??? Save/restore. */ +} + + +/* Board init. */ + +static struct arm_boot_info integrator_binfo = { + .loader_start = 0x0, + .board_id = 0x113, +}; + +static void integratorcp_init(QEMUMachineInitArgs *args) +{ + ram_addr_t ram_size = args->ram_size; + const char *cpu_model = args->cpu_model; + const char *kernel_filename = args->kernel_filename; + const char *kernel_cmdline = args->kernel_cmdline; + const char *initrd_filename = args->initrd_filename; + 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]; + qemu_irq *cpu_pic; + DeviceState *dev; + int i; + + if (!cpu_model) { + cpu_model = "arm926"; + } + cpu = cpu_arm_init(cpu_model); + if (!cpu) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + + memory_region_init_ram(ram, "integrator.ram", ram_size); + vmstate_register_ram_global(ram); + /* ??? 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, "ram.alias", ram, 0, ram_size); + memory_region_add_subregion(address_space_mem, 0x80000000, ram_alias); + + dev = qdev_create(NULL, "integrator_core"); + qdev_prop_set_uint32(dev, "memsz", ram_size >> 20); + qdev_init_nofail(dev); + sysbus_mmio_map((SysBusDevice *)dev, 0, 0x10000000); + + cpu_pic = arm_pic_init_cpu(cpu); + dev = sysbus_create_varargs("integrator_pic", 0x14000000, + cpu_pic[ARM_PIC_CPU_IRQ], + cpu_pic[ARM_PIC_CPU_FIQ], NULL); + for (i = 0; i < 32; i++) { + pic[i] = qdev_get_gpio_in(dev, i); + } + sysbus_create_simple("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_control_init(0xcb000000); + sysbus_create_simple("pl050_keyboard", 0x18000000, pic[3]); + sysbus_create_simple("pl050_mouse", 0x19000000, pic[4]); + sysbus_create_varargs("pl181", 0x1c000000, pic[23], pic[24], NULL); + 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, + .is_default = 1, + DEFAULT_MACHINE_OPTIONS, +}; + +static void integratorcp_machine_init(void) +{ + qemu_register_machine(&integratorcp_machine); +} + +machine_init(integratorcp_machine_init); + +static Property core_properties[] = { + DEFINE_PROP_UINT32("memsz", integratorcm_state, 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 = "integrator_core", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(integratorcm_state), + .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 = "integrator_pic", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(icp_pic_state), + .class_init = icp_pic_class_init, +}; + +static void integratorcp_register_types(void) +{ + type_register_static(&icp_pic_info); + type_register_static(&core_info); +} + +type_init(integratorcp_register_types) diff --git a/hw/arm/kzm.c b/hw/arm/kzm.c new file mode 100644 index 0000000000..ec50a319ac --- /dev/null +++ b/hw/arm/kzm.c @@ -0,0 +1,157 @@ +/* + * 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-misc.h" +#include "hw/devices.h" +#include "net/net.h" +#include "sysemu/sysemu.h" +#include "hw/boards.h" +#include "hw/serial.h" +#include "hw/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(QEMUMachineInitArgs *args) +{ + ram_addr_t ram_size = args->ram_size; + const char *cpu_model = args->cpu_model; + const char *kernel_filename = args->kernel_filename; + const char *kernel_cmdline = args->kernel_cmdline; + const char *initrd_filename = args->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); + qemu_irq *cpu_pic; + 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_init_ram(ram, "kzm.ram", ram_size); + vmstate_register_ram_global(ram); + memory_region_add_subregion(address_space_mem, KZM_RAMADDRESS, ram); + + memory_region_init_alias(ram_alias, "ram.alias", ram, 0, ram_size); + memory_region_add_subregion(address_space_mem, 0x88000000, ram_alias); + + memory_region_init_ram(sram, "kzm.sram", 0x4000); + memory_region_add_subregion(address_space_mem, 0x1FFFC000, sram); + + cpu_pic = arm_pic_init_cpu(cpu); + dev = sysbus_create_varargs("imx_avic", 0x68000000, + cpu_pic[ARM_PIC_CPU_IRQ], + cpu_pic[ARM_PIC_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, + DEFAULT_MACHINE_OPTIONS, +}; + +static void kzm_machine_init(void) +{ + qemu_register_machine(&kzm_machine); +} + +machine_init(kzm_machine_init) diff --git a/hw/arm/mainstone.c b/hw/arm/mainstone.c new file mode 100644 index 0000000000..aea908f036 --- /dev/null +++ b/hw/arm/mainstone.c @@ -0,0 +1,190 @@ +/* + * 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/pxa.h" +#include "hw/arm-misc.h" +#include "net/net.h" +#include "hw/devices.h" +#include "hw/boards.h" +#include "hw/flash.h" +#include "sysemu/blockdev.h" +#include "hw/sysbus.h" +#include "exec/address-spaces.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 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 */ + [0x39] = {5,2}, /* space */ + [0x39] = {5,3}, /* space */ + [0x1c] = {5,5}, /* enter */ + [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, + QEMUMachineInitArgs *args, + 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 = args->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, "mainstone.rom", MAINSTONE_ROM); + 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) { + 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, + dinfo->bdrv, 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 */ + printf("map addr %p\n", &map); + 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 = args->kernel_filename; + mainstone_binfo.kernel_cmdline = args->kernel_cmdline; + mainstone_binfo.initrd_filename = args->initrd_filename; + mainstone_binfo.board_id = arm_id; + arm_load_kernel(mpu->cpu, &mainstone_binfo); +} + +static void mainstone_init(QEMUMachineInitArgs *args) +{ + mainstone_common_init(get_system_memory(), args, mainstone, 0x196); +} + +static QEMUMachine mainstone2_machine = { + .name = "mainstone", + .desc = "Mainstone II (PXA27x)", + .init = mainstone_init, + DEFAULT_MACHINE_OPTIONS, +}; + +static void mainstone_machine_init(void) +{ + qemu_register_machine(&mainstone2_machine); +} + +machine_init(mainstone_machine_init); diff --git a/hw/arm/musicpal.c b/hw/arm/musicpal.c new file mode 100644 index 0000000000..a37dbd7961 --- /dev/null +++ b/hw/arm/musicpal.c @@ -0,0 +1,1697 @@ +/* + * 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-misc.h" +#include "hw/devices.h" +#include "net/net.h" +#include "sysemu/sysemu.h" +#include "hw/boards.h" +#include "hw/serial.h" +#include "qemu/timer.h" +#include "hw/ptimer.h" +#include "block/block.h" +#include "hw/flash.h" +#include "ui/console.h" +#include "hw/i2c.h" +#include "sysemu/blockdev.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 +#define MP_ETH_CTDP2 0x4E8 +#define MP_ETH_CTDP3 0x4EC + +/* 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 (1 << 31) + +/* RX descriptor status */ +#define MP_ETH_RX_OWN (1 << 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; + +typedef struct mv88w8618_eth_state { + SysBusDevice busdev; + 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, (void *)desc, sizeof(*desc)); +} + +static void eth_rx_desc_get(uint32_t addr, mv88w8618_rx_desc *desc) +{ + cpu_physical_memory_read(addr, (void *)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 int eth_can_receive(NetClientState *nc) +{ + return 1; +} + +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, (void *)desc, sizeof(*desc)); +} + +static void eth_tx_desc_get(uint32_t addr, mv88w8618_tx_desc *desc) +{ + cpu_physical_memory_read(addr, (void *)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_CTDP3: + 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_CTDP3: + 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), + .can_receive = eth_can_receive, + .receive = eth_receive, + .cleanup = eth_cleanup, +}; + +static int mv88w8618_eth_init(SysBusDevice *dev) +{ + mv88w8618_eth_state *s = FROM_SYSBUS(mv88w8618_eth_state, dev); + + sysbus_init_irq(dev, &s->irq); + s->nic = qemu_new_nic(&net_mv88w8618_info, &s->conf, + object_get_typename(OBJECT(dev)), dev->qdev.id, s); + memory_region_init_io(&s->iomem, &mv88w8618_eth_ops, s, "mv88w8618-eth", + MP_ETH_SIZE); + sysbus_init_mmio(dev, &s->iomem); + return 0; +} + +static const VMStateDescription mv88w8618_eth_vmsd = { + .name = "mv88w8618_eth", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 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 = "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 */ + +typedef struct musicpal_lcd_state { + SysBusDevice busdev; + MemoryRegion iomem; + uint32_t brightness; + uint32_t mode; + uint32_t irqctrl; + uint32_t page; + uint32_t page_off; + DisplayState *ds; + 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; \ + type *pixel = &((type *) ds_get_data(s->ds))[(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; + int x, y, col; + + switch (ds_get_bits_per_pixel(s->ds)) { + 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(s->ds->surface) ? + rgb_to_pixel32bgr : rgb_to_pixel32)) + default: + hw_error("unsupported colour depth %i\n", + ds_get_bits_per_pixel(s->ds)); + } + + dpy_gfx_update(s->ds, 0, 0, 128*3, 64*3); +} + +static void lcd_invalidate(void *opaque) +{ +} + +static void musicpal_lcd_gpio_brigthness_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 int musicpal_lcd_init(SysBusDevice *dev) +{ + musicpal_lcd_state *s = FROM_SYSBUS(musicpal_lcd_state, dev); + + s->brightness = 7; + + memory_region_init_io(&s->iomem, &musicpal_lcd_ops, s, + "musicpal-lcd", MP_LCD_SIZE); + sysbus_init_mmio(dev, &s->iomem); + + s->ds = graphic_console_init(lcd_refresh, lcd_invalidate, + NULL, NULL, s); + qemu_console_resize(s->ds, 128*3, 64*3); + + qdev_init_gpio_in(&dev->qdev, musicpal_lcd_gpio_brigthness_in, 3); + + return 0; +} + +static const VMStateDescription musicpal_lcd_vmsd = { + .name = "musicpal_lcd", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 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 = "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 + +typedef struct mv88w8618_pic_state +{ + SysBusDevice busdev; + 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 = FROM_SYSBUS(mv88w8618_pic_state, + SYS_BUS_DEVICE(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 = FROM_SYSBUS(mv88w8618_pic_state, dev); + + qdev_init_gpio_in(&dev->qdev, mv88w8618_pic_set_irq, 32); + sysbus_init_irq(dev, &s->parent_irq); + memory_region_init_io(&s->iomem, &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, + .minimum_version_id_old = 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 = "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; + +typedef struct mv88w8618_pit_state { + SysBusDevice busdev; + 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 = FROM_SYSBUS(mv88w8618_pit_state, + SYS_BUS_DEVICE(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 = FROM_SYSBUS(mv88w8618_pit_state, 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, &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, + .minimum_version_id_old = 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, + .minimum_version_id_old = 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 = "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 + +typedef struct mv88w8618_flashcfg_state { + SysBusDevice busdev; + 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 = FROM_SYSBUS(mv88w8618_flashcfg_state, dev); + + s->cfgr0 = 0xfffe4285; /* Default as set by U-Boot for 8 MB flash */ + memory_region_init_io(&s->iomem, &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, + .minimum_version_id_old = 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 = "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 + +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(SysBusDevice *dev) +{ + MemoryRegion *iomem = g_new(MemoryRegion, 1); + + memory_region_init_io(iomem, &musicpal_misc_ops, NULL, + "musicpal-misc", MP_MISC_SIZE); + sysbus_add_memory(dev, MP_MISC_BASE, iomem); +} + +/* 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, &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 + +typedef struct musicpal_gpio_state { + SysBusDevice busdev; + 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 = FROM_SYSBUS(musicpal_gpio_state, + SYS_BUS_DEVICE(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 *dev) +{ + musicpal_gpio_state *s = FROM_SYSBUS(musicpal_gpio_state, dev); + + sysbus_init_irq(dev, &s->irq); + + memory_region_init_io(&s->iomem, &musicpal_gpio_ops, s, + "musicpal-gpio", MP_GPIO_SIZE); + sysbus_init_mmio(dev, &s->iomem); + + qdev_init_gpio_out(&dev->qdev, s->out, ARRAY_SIZE(s->out)); + + qdev_init_gpio_in(&dev->qdev, musicpal_gpio_pin_event, 32); + + return 0; +} + +static const VMStateDescription musicpal_gpio_vmsd = { + .name = "musicpal_gpio", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 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 = "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) + +typedef struct musicpal_key_state { + SysBusDevice busdev; + 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 *dev) +{ + musicpal_key_state *s = FROM_SYSBUS(musicpal_key_state, dev); + + memory_region_init(&s->iomem, "dummy", 0); + sysbus_init_mmio(dev, &s->iomem); + + s->kbd_extended = 0; + s->pressed_keys = 0; + + qdev_init_gpio_out(&dev->qdev, 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, + .minimum_version_id_old = 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 = "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(QEMUMachineInitArgs *args) +{ + const char *cpu_model = args->cpu_model; + const char *kernel_filename = args->kernel_filename; + const char *kernel_cmdline = args->kernel_cmdline; + const char *initrd_filename = args->initrd_filename; + ARMCPU *cpu; + qemu_irq *cpu_pic; + qemu_irq pic[32]; + DeviceState *dev; + DeviceState *i2c_dev; + DeviceState *lcd_dev; + DeviceState *key_dev; + DeviceState *wm8750_dev; + SysBusDevice *s; + i2c_bus *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); + } + cpu_pic = arm_pic_init_cpu(cpu); + + /* For now we use a fixed - the original - RAM size */ + memory_region_init_ram(ram, "musicpal.ram", MP_RAM_DEFAULT_SIZE); + vmstate_register_ram_global(ram); + memory_region_add_subregion(address_space_mem, 0, ram); + + memory_region_init_ram(sram, "musicpal.sram", MP_SRAM_SIZE); + vmstate_register_ram_global(sram); + memory_region_add_subregion(address_space_mem, MP_SRAM_BASE, sram); + + dev = sysbus_create_simple("mv88w8618_pic", MP_PIC_BASE, + cpu_pic[ARM_PIC_CPU_IRQ]); + for (i = 0; i < 32; i++) { + pic[i] = qdev_get_gpio_in(dev, i); + } + sysbus_create_varargs("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) { + flash_size = bdrv_getlength(dinfo->bdrv); + 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, + dinfo->bdrv, 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, + dinfo->bdrv, 0x10000, + (flash_size + 0xffff) >> 16, + MP_FLASH_SIZE_MAX / flash_size, + 2, 0x00BF, 0x236D, 0x0000, 0x0000, + 0x5555, 0x2AAA, 0); +#endif + + } + sysbus_create_simple("mv88w8618_flashcfg", MP_FLASHCFG_BASE, NULL); + + qemu_check_nic_model(&nd_table[0], "mv88w8618"); + dev = qdev_create(NULL, "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); + + musicpal_misc_init(SYS_BUS_DEVICE(dev)); + + dev = sysbus_create_simple("musicpal_gpio", MP_GPIO_BASE, pic[MP_GPIO_IRQ]); + i2c_dev = sysbus_create_simple("gpio_i2c", -1, NULL); + i2c = (i2c_bus *)qdev_get_child_bus(i2c_dev, "i2c"); + + lcd_dev = sysbus_create_simple("musicpal_lcd", MP_LCD_BASE, NULL); + key_dev = sysbus_create_simple("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, + DEFAULT_MACHINE_OPTIONS, +}; + +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_init(musicpal_register_types) diff --git a/hw/arm/nseries.c b/hw/arm/nseries.c new file mode 100644 index 0000000000..c5bf9f95b3 --- /dev/null +++ b/hw/arm/nseries.c @@ -0,0 +1,1430 @@ +/* + * 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/omap.h" +#include "hw/arm-misc.h" +#include "hw/irq.h" +#include "ui/console.h" +#include "hw/boards.h" +#include "hw/i2c.h" +#include "hw/devices.h" +#include "hw/flash.h" +#include "hw/hw.h" +#include "hw/bt.h" +#include "hw/loader.h" +#include "sysemu/blockdev.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); + + printf("%s: MMC slot %i active\n", __FUNCTION__, level + 1); +} + +static void n8x0_gpio_setup(struct n800_s *s) +{ + qemu_irq *mmc_cs = qemu_allocate_irqs(n800_mmc_cs_cb, s->mpu->mmc, 1); + qdev_connect_gpio_out(s->mpu->gpio, N8X0_MMC_CS_GPIO, mmc_cs[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 && dinfo->bdrv) { + qdev_prop_set_drive_nofail(s->nand, "drive", dinfo->bdrv); + } + 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); + i2c_bus *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) +{ + if (!s->sleep) + fprintf(stderr, "%s: Display off\n", __FUNCTION__); + + 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 = ffs(s->param[0] & 0xf) - 1; + else if (s->pm < 0) + s->pm = 1; + break; + + case 0x28: /* DISPOFF */ + s->onoff = 0; + fprintf(stderr, "%s: Display off\n", __FUNCTION__); + break; + case 0x29: /* DISPON */ + s->onoff = 1; + fprintf(stderr, "%s: Display on\n", __FUNCTION__); + 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: + fprintf(stderr, "%s: unknown command %02x\n", __FUNCTION__, 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_raw(p + 10, 0x04f70000); + strcpy((void *) (p + 9), "RX-34"); + + /* RAM size in MB? */ + stl_raw(p + 12, 0x80); + + /* Pointer to the list of tags */ + stl_raw(p + 13, OMAP2_SRAM_BASE + 0x9000); + + /* The NOLO tags start here */ + p = sram_base + 0x9000; +#define ADD_TAG(tag, len) \ + stw_raw((uint16_t *) p + 0, tag); \ + stw_raw((uint16_t *) p + 1, len); p ++; \ + stl_raw(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_raw(v ++, n800_pinout[i]); + + /* Kernel memsize? */ + ADD_TAG(0x6e05, 1); + stl_raw(v ++, 2); + + /* NOLO serial console */ + ADD_TAG(0x6e02, 4); + stl_raw(v ++, XLDR_LL_UART); /* UART number (1 - 3) */ + +#if 0 + /* CBUS settings (Retu/AVilma) */ + ADD_TAG(0x6e03, 6); + stw_raw((uint16_t *) v + 0, 65); /* CBUS GPIO0 */ + stw_raw((uint16_t *) v + 1, 66); /* CBUS GPIO1 */ + stw_raw((uint16_t *) v + 2, 64); /* CBUS GPIO2 */ + v += 2; +#endif + + /* Nokia ASIC BB5 (Retu/Tahvo) */ + ADD_TAG(0x6e0a, 4); + stw_raw((uint16_t *) v + 0, 111); /* "Retu" interrupt GPIO */ + stw_raw((uint16_t *) v + 1, 108); /* "Tahvo" interrupt GPIO */ + v ++; + + /* LCD console? */ + ADD_TAG(0x6e04, 4); + stw_raw((uint16_t *) v + 0, 30); /* ??? */ + stw_raw((uint16_t *) v + 1, 24); /* ??? */ + v ++; + +#if 0 + /* LCD settings */ + ADD_TAG(0x6e06, 2); + stw_raw((uint16_t *) (v ++), 15); /* ??? */ +#endif + + /* I^2C (Menelaus) */ + ADD_TAG(0x6e07, 4); + stl_raw(v ++, 0x00720000); /* ??? */ + + /* Unknown */ + ADD_TAG(0x6e0b, 6); + stw_raw((uint16_t *) v + 0, 94); /* ??? */ + stw_raw((uint16_t *) v + 1, 23); /* ??? */ + stw_raw((uint16_t *) v + 2, 0); /* ??? */ + v += 2; + + /* OMAP gpio switch info */ + ADD_TAG(0x6e0c, 80); + strcpy((void *) v, "bat_cover"); v += 3; + stw_raw((uint16_t *) v + 0, 110); /* GPIO num ??? */ + stw_raw((uint16_t *) v + 1, 1); /* GPIO num ??? */ + v += 2; + strcpy((void *) v, "cam_act"); v += 3; + stw_raw((uint16_t *) v + 0, 95); /* GPIO num ??? */ + stw_raw((uint16_t *) v + 1, 32); /* GPIO num ??? */ + v += 2; + strcpy((void *) v, "cam_turn"); v += 3; + stw_raw((uint16_t *) v + 0, 12); /* GPIO num ??? */ + stw_raw((uint16_t *) v + 1, 33); /* GPIO num ??? */ + v += 2; + strcpy((void *) v, "headphone"); v += 3; + stw_raw((uint16_t *) v + 0, 107); /* GPIO num ??? */ + stw_raw((uint16_t *) v + 1, 17); /* GPIO num ??? */ + v += 2; + + /* Bluetooth */ + ADD_TAG(0x6e0e, 12); + stl_raw(v ++, 0x5c623d01); /* ??? */ + stl_raw(v ++, 0x00000201); /* ??? */ + stl_raw(v ++, 0x00000000); /* ??? */ + + /* CX3110x WLAN settings */ + ADD_TAG(0x6e0f, 8); + stl_raw(v ++, 0x00610025); /* ??? */ + stl_raw(v ++, 0xffff0057); /* ??? */ + + /* MMC host settings */ + ADD_TAG(0x6e10, 12); + stl_raw(v ++, 0xffff000f); /* ??? */ + stl_raw(v ++, 0xffffffff); /* ??? */ + stl_raw(v ++, 0x00000060); /* ??? */ + + /* OneNAND chip select */ + ADD_TAG(0x6e11, 10); + stl_raw(v ++, 0x00000401); /* ??? */ + stl_raw(v ++, 0x0002003a); /* ??? */ + stl_raw(v ++, 0x00000002); /* ??? */ + + /* TEA5761 sensor settings */ + ADD_TAG(0x6e12, 2); + stl_raw(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_raw(p ++, 0x00000000); + stl_raw(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 */ + (void *) &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, (void *) &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_raw(w ++, OMAP_TAG_UART); /* u16 tag */ + stw_raw(w ++, 4); /* u16 len */ + stw_raw(w ++, (1 << 2) | (1 << 1) | (1 << 0)); /* uint enabled_uarts */ + w ++; + +#if 0 + stw_raw(w ++, OMAP_TAG_SERIAL_CONSOLE); /* u16 tag */ + stw_raw(w ++, 4); /* u16 len */ + stw_raw(w ++, XLDR_LL_UART + 1); /* u8 console_uart */ + stw_raw(w ++, 115200); /* u32 console_speed */ +#endif + + stw_raw(w ++, OMAP_TAG_LCD); /* u16 tag */ + stw_raw(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_raw(w ++, N810_BLIZZARD_RESET_GPIO); /* TODO: n800 s16 nreset_gpio */ + stw_raw(w ++, 24); /* u8 data_lines */ + + stw_raw(w ++, OMAP_TAG_CBUS); /* u16 tag */ + stw_raw(w ++, 8); /* u16 len */ + stw_raw(w ++, N8X0_CBUS_CLK_GPIO); /* s16 clk_gpio */ + stw_raw(w ++, N8X0_CBUS_DAT_GPIO); /* s16 dat_gpio */ + stw_raw(w ++, N8X0_CBUS_SEL_GPIO); /* s16 sel_gpio */ + w ++; + + stw_raw(w ++, OMAP_TAG_EM_ASIC_BB5); /* u16 tag */ + stw_raw(w ++, 4); /* u16 len */ + stw_raw(w ++, N8X0_RETU_GPIO); /* s16 retu_irq_gpio */ + stw_raw(w ++, N8X0_TAHVO_GPIO); /* s16 tahvo_irq_gpio */ + + gpiosw = (model == 810) ? n810_gpiosw_info : n800_gpiosw_info; + for (; gpiosw->name; gpiosw ++) { + stw_raw(w ++, OMAP_TAG_GPIO_SWITCH); /* u16 tag */ + stw_raw(w ++, 20); /* u16 len */ + strcpy((void *) w, gpiosw->name); /* char name[12] */ + w += 6; + stw_raw(w ++, gpiosw->line); /* u16 gpio */ + stw_raw(w ++, gpiosw->type); + stw_raw(w ++, 0); + stw_raw(w ++, 0); + } + + stw_raw(w ++, OMAP_TAG_NOKIA_BT); /* u16 tag */ + stw_raw(w ++, 12); /* u16 len */ + b = (void *) w; + stb_raw(b ++, 0x01); /* u8 chip_type (CSR) */ + stb_raw(b ++, N8X0_BT_WKUP_GPIO); /* u8 bt_wakeup_gpio */ + stb_raw(b ++, N8X0_BT_HOST_WKUP_GPIO); /* u8 host_wakeup_gpio */ + stb_raw(b ++, N8X0_BT_RESET_GPIO); /* u8 reset_gpio */ + stb_raw(b ++, BT_UART + 1); /* u8 bt_uart */ + memcpy(b, &n8x0_bd_addr, 6); /* u8 bd_addr[6] */ + b += 6; + stb_raw(b ++, 0x02); /* u8 bt_sysclk (38.4) */ + w = (void *) b; + + stw_raw(w ++, OMAP_TAG_WLAN_CX3110X); /* u16 tag */ + stw_raw(w ++, 8); /* u16 len */ + stw_raw(w ++, 0x25); /* u8 chip_type */ + stw_raw(w ++, N8X0_WLAN_PWR_GPIO); /* s16 power_gpio */ + stw_raw(w ++, N8X0_WLAN_IRQ_GPIO); /* s16 irq_gpio */ + stw_raw(w ++, -1); /* s16 spi_cs_gpio */ + + stw_raw(w ++, OMAP_TAG_MMC); /* u16 tag */ + stw_raw(w ++, 16); /* u16 len */ + if (model == 810) { + stw_raw(w ++, 0x23f); /* unsigned flags */ + stw_raw(w ++, -1); /* s16 power_pin */ + stw_raw(w ++, -1); /* s16 switch_pin */ + stw_raw(w ++, -1); /* s16 wp_pin */ + stw_raw(w ++, 0x240); /* unsigned flags */ + stw_raw(w ++, 0xc000); /* s16 power_pin */ + stw_raw(w ++, 0x0248); /* s16 switch_pin */ + stw_raw(w ++, 0xc000); /* s16 wp_pin */ + } else { + stw_raw(w ++, 0xf); /* unsigned flags */ + stw_raw(w ++, -1); /* s16 power_pin */ + stw_raw(w ++, -1); /* s16 switch_pin */ + stw_raw(w ++, -1); /* s16 wp_pin */ + stw_raw(w ++, 0); /* unsigned flags */ + stw_raw(w ++, 0); /* s16 power_pin */ + stw_raw(w ++, 0); /* s16 switch_pin */ + stw_raw(w ++, 0); /* s16 wp_pin */ + } + + stw_raw(w ++, OMAP_TAG_TEA5761); /* u16 tag */ + stw_raw(w ++, 4); /* u16 len */ + stw_raw(w ++, N8X0_TEA5761_CS_GPIO); /* u16 enable_gpio */ + w ++; + + partition = (model == 810) ? n810_part_info : n800_part_info; + for (; partition->name; partition ++) { + stw_raw(w ++, OMAP_TAG_PARTITION); /* u16 tag */ + stw_raw(w ++, 28); /* u16 len */ + strcpy((void *) w, partition->name); /* char name[16] */ + l = (void *) (w + 8); + stl_raw(l ++, partition->size); /* unsigned int size */ + stl_raw(l ++, partition->offset); /* unsigned int offset */ + stl_raw(l ++, partition->mask); /* unsigned int mask_flags */ + w = (void *) l; + } + + stw_raw(w ++, OMAP_TAG_BOOT_REASON); /* u16 tag */ + stw_raw(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_raw(w ++, OMAP_TAG_VERSION_STR); /* u16 tag */ + stw_raw(w ++, 24); /* u16 len */ + strcpy((void *) w, "product"); /* char component[12] */ + w += 6; + strcpy((void *) w, tag); /* char version[12] */ + w += 6; + + stw_raw(w ++, OMAP_TAG_VERSION_STR); /* u16 tag */ + stw_raw(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_raw(w ++, OMAP_TAG_VERSION_STR); /* u16 tag */ + stw_raw(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(QEMUMachineInitArgs *args, + 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; + DisplayState *ds; + + s->mpu = omap2420_mpu_init(sysmem, sdram_size, args->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(false)) { + n8x0_usb_setup(s); + } + + if (args->kernel_filename) { + /* Or at the linux loader. */ + binfo->kernel_filename = args->kernel_filename; + binfo->kernel_cmdline = args->kernel_cmdline; + binfo->initrd_filename = args->initrd_filename; + arm_load_kernel(s->mpu->cpu, binfo); + + qemu_register_reset(n8x0_boot_init, s); + } + + if (option_rom[0].name && + (args->boot_device[0] == 'n' || !args->kernel_filename)) { + int rom_size; + 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. */ + rom_size = load_image_targphys(option_rom[0].name, + OMAP2_Q2_BASE + 0x400000, + sdram_size - 0x400000); + printf("%i bytes of image loaded\n", rom_size); + + n800_setup_nolo_tags(nolo_tags); + cpu_physical_memory_write(OMAP2_SRAM_BASE, nolo_tags, 0x10000); + } + /* FIXME: We shouldn't really be doing this here. The LCD controller + will set the size once configured, so this just sets an initial + size until the guest activates the display. */ + ds = get_displaystate(); + ds->surface = qemu_resize_displaysurface(ds, 800, 480); + dpy_gfx_resize(ds); +} + +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(QEMUMachineInitArgs *args) +{ + return n8x0_init(args, &n800_binfo, 800); +} + +static void n810_init(QEMUMachineInitArgs *args) +{ + return n8x0_init(args, &n810_binfo, 810); +} + +static QEMUMachine n800_machine = { + .name = "n800", + .desc = "Nokia N800 tablet aka. RX-34 (OMAP2420)", + .init = n800_init, + DEFAULT_MACHINE_OPTIONS, +}; + +static QEMUMachine n810_machine = { + .name = "n810", + .desc = "Nokia N810 tablet aka. RX-44 (OMAP2420)", + .init = n810_init, + DEFAULT_MACHINE_OPTIONS, +}; + +static void nseries_machine_init(void) +{ + qemu_register_machine(&n800_machine); + qemu_register_machine(&n810_machine); +} + +machine_init(nseries_machine_init); diff --git a/hw/arm/omap1.c b/hw/arm/omap1.c new file mode 100644 index 0000000000..6f0a8ca074 --- /dev/null +++ b/hw/arm/omap1.c @@ -0,0 +1,4056 @@ +/* + * 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/hw.h" +#include "hw/arm-misc.h" +#include "hw/omap.h" +#include "sysemu/sysemu.h" +#include "hw/soc_dma.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, (void *) &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, (void *) &val8, 1); +} + +uint32_t omap_badwidth_read16(void *opaque, hwaddr addr) +{ + uint16_t ret; + + OMAP_16B_REG(addr); + cpu_physical_memory_read(addr, (void *) &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, (void *) &val16, 2); +} + +uint32_t omap_badwidth_read32(void *opaque, hwaddr addr) +{ + uint32_t ret; + + OMAP_32B_REG(addr); + cpu_physical_memory_read(addr, (void *) &ret, 4); + return ret; +} + +void omap_badwidth_write32(void *opaque, hwaddr addr, + uint32_t value) +{ + OMAP_32B_REG(addr); + cpu_physical_memory_write(addr, (void *) &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_get_clock_ns(vm_clock) - 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_get_clock_ns(vm_clock); +} + +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) + qemu_mod_timer(timer->timer, timer->time + expires); + else + qemu_bh_schedule(timer->tick); + } else + qemu_del_timer(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_irqs(omap_timer_clk_update, timer, 1)[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) { + return omap_badwidth_write32(opaque, addr, value); + } + + 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) +{ + qemu_del_timer(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 = qemu_new_timer_ns(vm_clock, 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, &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) { + return omap_badwidth_write16(opaque, addr, value); + } + + 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) +{ + qemu_del_timer(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 = qemu_new_timer_ns(vm_clock, omap_timer_tick, &s->timer); + omap_wd_timer_reset(s); + omap_timer_clk_setup(&s->timer); + + memory_region_init_io(&s->iomem, &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) { + return omap_badwidth_write32(opaque, addr, value); + } + + 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) +{ + qemu_del_timer(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 = qemu_new_timer_ns(vm_clock, omap_timer_tick, &s->timer); + omap_os_timer_reset(s); + omap_timer_clk_setup(&s->timer); + + memory_region_init_io(&s->iomem, &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) { + return omap_badwidth_write16(opaque, addr, value); + } + + 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_get_clock_ns(vm_clock); + + 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, &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 & (1 << 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 & (1 << 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) { + return omap_badwidth_write32(opaque, addr, value); + } + + 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, &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) { + return omap_badwidth_write32(opaque, addr, value); + } + + 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, &omap_id_ops, mpu, + "omap-id", 0x100000000ULL); + memory_region_init_alias(&mpu->id_iomem_e18, "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, "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, "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) { + return omap_badwidth_write32(opaque, addr, value); + } + + 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, &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) { + return omap_badwidth_write16(opaque, addr, value); + } + + 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, &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) { + return omap_badwidth_write32(opaque, addr, value); + } + + 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, &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) { + return omap_badwidth_write16(opaque, addr, value); + } + + 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, &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(&s->cpu->env, 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) { + return omap_badwidth_write16(opaque, addr, value); + } + + 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; + + 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 */ + return (s->clkm.clocking_scheme << 11) | s->clkm.cold_start | + (s->cpu->env.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) { + return omap_badwidth_write16(opaque, addr, value); + } + + 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, &omap_clkm_ops, s, + "omap-clkm", 0x100); + memory_region_init_io(&s->clkdsp_iomem, &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) { + return omap_badwidth_write16(opaque, addr, value); + } + + switch (offset) { + case 0x04: /* OUTPUT_REG */ + diff = (s->outputs ^ value) & ~s->dir; + s->outputs = value; + while ((ln = ffs(diff))) { + ln --; + 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 = ffs(diff))) { + ln --; + 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, &omap_mpuio_ops, s, + "omap-mpuio", 0x800); + memory_region_add_subregion(memory, base, &s->iomem); + + omap_clk_adduser(clk, qemu_allocate_irqs(omap_mpuio_onoff, s, 1)[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) { + return omap_badwidth_write16(opaque, addr, value); + } + + 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, &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) { + return omap_badwidth_write8(opaque, addr, value); + } + + 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, &omap_pwl_ops, s, + "omap-pwl", 0x800); + memory_region_add_subregion(system_memory, base, &s->iomem); + + omap_clk_adduser(clk, qemu_allocate_irqs(omap_pwl_clk_update, s, 1)[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) { + return omap_badwidth_write8(opaque, addr, value); + } + + 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, &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) { + return omap_badwidth_write8(opaque, addr, value); + } + + 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 -= (s->current_tm.tm_year % 100) * 31536000; + s->ti += 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; + + qemu_mod_timer(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_get_clock_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 = qemu_new_timer_ms(rtc_clock, omap_rtc_tick, s); + + omap_rtc_reset(s); + + memory_region_init_io(&s->iomem, &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); + qemu_mod_timer(s->source_timer, qemu_get_clock_ns(vm_clock) + + 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) +{ + qemu_del_timer(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); + qemu_mod_timer(s->sink_timer, qemu_get_clock_ns(vm_clock) + + 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); + qemu_del_timer(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: return omap_mcbsp_writeh(opaque, addr, value); + case 4: return omap_mcbsp_writew(opaque, addr, value); + default: return 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; + qemu_del_timer(s->source_timer); + qemu_del_timer(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 = qemu_new_timer_ns(vm_clock, omap_mcbsp_sink_tick, s); + s->source_timer = qemu_new_timer_ns(vm_clock, omap_mcbsp_source_tick, s); + omap_mcbsp_reset(s); + + memory_region_init_io(&s->iomem, &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_irqs(omap_mcbsp_i2s_swallow, s, 1)[0]; + slave->tx_start = qemu_allocate_irqs(omap_mcbsp_i2s_start, s, 1)[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) + qemu_mod_timer(s->tm, qemu_get_clock_ms(vm_clock) + s->period - s->on); + else + qemu_mod_timer(s->tm, qemu_get_clock_ms(vm_clock) + 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 */ + } + + qemu_del_timer(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) { + return omap_badwidth_write8(opaque, addr, value); + } + + 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 = qemu_new_timer_ms(vm_clock, omap_lpg_tick, s); + + omap_lpg_reset(s); + + memory_region_init_io(&s->iomem, &omap_lpg_ops, s, "omap-lpg", 0x800); + memory_region_add_subregion(system_memory, base, &s->iomem); + + omap_clk_adduser(clk, qemu_allocate_irqs(omap_lpg_clk_update, s, 1)[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, &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, 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; + + if (mpu->cpu->env.halted) { + cpu_interrupt(&mpu->cpu->env, 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 *cpu_irq; + 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_irqs(omap_mpu_wakeup, s, 1)[0]; + + /* Clocks */ + omap_clk_init(s); + + /* Memory-mapped stuff */ + memory_region_init_ram(&s->emiff_ram, "omap1.dram", s->sdram_size); + vmstate_register_ram_global(&s->emiff_ram); + memory_region_add_subregion(system_memory, OMAP_EMIFF_BASE, &s->emiff_ram); + memory_region_init_ram(&s->imif_ram, "omap1.sram", s->sram_size); + 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); + + cpu_irq = arm_pic_init_cpu(s->cpu); + 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, cpu_irq[ARM_PIC_CPU_IRQ]); + sysbus_connect_irq(busdev, 1, cpu_irq[ARM_PIC_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, dinfo->bdrv, + 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/hw/arm/omap2.c b/hw/arm/omap2.c new file mode 100644 index 0000000000..0a2cd7bab6 --- /dev/null +++ b/hw/arm/omap2.c @@ -0,0 +1,2684 @@ +/* + * 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/blockdev.h" +#include "hw/hw.h" +#include "hw/arm-misc.h" +#include "hw/omap.h" +#include "sysemu/sysemu.h" +#include "qemu/timer.h" +#include "char/char.h" +#include "hw/flash.h" +#include "hw/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) { + return omap_badwidth_write16(opaque, addr, value); + } + + 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, &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) { + return omap_badwidth_write32(opaque, addr, value); + } + + 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) { + return omap_badwidth_write8(opaque, addr, size); + } + + 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, &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, &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) { + return omap_badwidth_write32(opaque, addr, value); + } + + 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, &omap_prcm_ops, s, "omap.pcrm0", + omap_l4_region_size(ta, 0)); + memory_region_init_io(&s->iomem1, &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, &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 *cpu_irq; + 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_irqs(omap_mpu_wakeup, s, 1)[0]; + + /* Clocks */ + omap_clk_init(s); + + /* Memory-mapped stuff */ + memory_region_init_ram(&s->sdram, "omap2.dram", s->sdram_size); + vmstate_register_ram_global(&s->sdram); + memory_region_add_subregion(sysmem, OMAP2_Q2_BASE, &s->sdram); + memory_region_init_ram(&s->sram, "omap2.sram", s->sram_size); + 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 */ + cpu_irq = arm_pic_init_cpu(s->cpu); + 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, cpu_irq[ARM_PIC_CPU_IRQ]); + sysbus_connect_irq(busdev, 1, cpu_irq[ARM_PIC_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), dinfo->bdrv, + 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/hw/arm/omap_sx1.c b/hw/arm/omap_sx1.c new file mode 100644 index 0000000000..85982334bd --- /dev/null +++ b/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/omap.h" +#include "hw/boards.h" +#include "hw/arm-misc.h" +#include "hw/flash.h" +#include "sysemu/blockdev.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(QEMUMachineInitArgs *args, const int version) +{ + struct omap_mpu_state_s *mpu; + MemoryRegion *address_space = get_system_memory(); + MemoryRegion *flash = g_new(MemoryRegion, 1); + MemoryRegion *flash_1 = 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, args->cpu_model); + + /* External Flash (EMIFS) */ + memory_region_init_ram(flash, "omap_sx1.flash0-0", flash_size); + 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], &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], &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], &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, + dinfo->bdrv, 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) { + memory_region_init_ram(flash_1, "omap_sx1.flash1-0", flash1_size); + 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], &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, + dinfo->bdrv, 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], &static_ops, &cs1val, + "sx1.cs1", OMAP_CS1_SIZE); + memory_region_add_subregion(address_space, + OMAP_CS1_BASE, &cs[1]); + } + + if (!args->kernel_filename && !fl_idx) { + fprintf(stderr, "Kernel or Flash image must be specified\n"); + exit(1); + } + + /* Load the kernel. */ + if (args->kernel_filename) { + sx1_binfo.kernel_filename = args->kernel_filename; + sx1_binfo.kernel_cmdline = args->kernel_cmdline; + sx1_binfo.initrd_filename = args->initrd_filename; + arm_load_kernel(mpu->cpu, &sx1_binfo); + } + + /* TODO: fix next line */ + //~ qemu_console_resize(ds, 640, 480); +} + +static void sx1_init_v1(QEMUMachineInitArgs *args) +{ + sx1_init(args, 1); +} + +static void sx1_init_v2(QEMUMachineInitArgs *args) +{ + sx1_init(args, 2); +} + +static QEMUMachine sx1_machine_v2 = { + .name = "sx1", + .desc = "Siemens SX1 (OMAP310) V2", + .init = sx1_init_v2, + DEFAULT_MACHINE_OPTIONS, +}; + +static QEMUMachine sx1_machine_v1 = { + .name = "sx1-v1", + .desc = "Siemens SX1 (OMAP310) V1", + .init = sx1_init_v1, + DEFAULT_MACHINE_OPTIONS, +}; + +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/hw/arm/palm.c b/hw/arm/palm.c new file mode 100644 index 0000000000..91bc74af24 --- /dev/null +++ b/hw/arm/palm.c @@ -0,0 +1,291 @@ +/* + * 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 "ui/console.h" +#include "hw/omap.h" +#include "hw/boards.h" +#include "hw/arm-misc.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(QEMUMachineInitArgs *args) +{ + const char *cpu_model = args->cpu_model; + const char *kernel_filename = args->kernel_filename; + const char *kernel_cmdline = args->kernel_cmdline; + const char *initrd_filename = args->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; + DisplayState *ds = get_displaystate(); + 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, "palmte.flash", flash_size); + 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], &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], &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], &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], &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) { + fprintf(stderr, "Kernel or ROM image must be specified\n"); + exit(1); + } + + /* Load the kernel. */ + if (kernel_filename) { + 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); + } + + /* FIXME: We shouldn't really be doing this here. The LCD controller + will set the size once configured, so this just sets an initial + size until the guest activates the display. */ + ds->surface = qemu_resize_displaysurface(ds, 320, 320); + dpy_gfx_resize(ds); +} + +static QEMUMachine palmte_machine = { + .name = "cheetah", + .desc = "Palm Tungsten|E aka. Cheetah PDA (OMAP310)", + .init = palmte_init, + DEFAULT_MACHINE_OPTIONS, +}; + +static void palmte_machine_init(void) +{ + qemu_register_machine(&palmte_machine); +} + +machine_init(palmte_machine_init); diff --git a/hw/arm/pic_cpu.c b/hw/arm/pic_cpu.c new file mode 100644 index 0000000000..82236006d2 --- /dev/null +++ b/hw/arm/pic_cpu.c @@ -0,0 +1,66 @@ +/* + * Generic ARM Programmable Interrupt Controller support. + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the LGPL + */ + +#include "hw/hw.h" +#include "hw/arm-misc.h" +#include "sysemu/kvm.h" + +/* Input 0 is IRQ and input 1 is FIQ. */ +static void arm_pic_cpu_handler(void *opaque, int irq, int level) +{ + ARMCPU *cpu = opaque; + CPUARMState *env = &cpu->env; + + switch (irq) { + case ARM_PIC_CPU_IRQ: + if (level) + cpu_interrupt(env, CPU_INTERRUPT_HARD); + else + cpu_reset_interrupt(env, CPU_INTERRUPT_HARD); + break; + case ARM_PIC_CPU_FIQ: + if (level) + cpu_interrupt(env, CPU_INTERRUPT_FIQ); + else + cpu_reset_interrupt(env, CPU_INTERRUPT_FIQ); + break; + default: + hw_error("arm_pic_cpu_handler: Bad interrupt line %d\n", irq); + } +} + +static void kvm_arm_pic_cpu_handler(void *opaque, int irq, int level) +{ +#ifdef CONFIG_KVM + ARMCPU *cpu = opaque; + CPUState *cs = CPU(cpu); + int kvm_irq = KVM_ARM_IRQ_TYPE_CPU << KVM_ARM_IRQ_TYPE_SHIFT; + + switch (irq) { + case ARM_PIC_CPU_IRQ: + kvm_irq |= KVM_ARM_IRQ_CPU_IRQ; + break; + case ARM_PIC_CPU_FIQ: + kvm_irq |= KVM_ARM_IRQ_CPU_FIQ; + break; + default: + hw_error("kvm_arm_pic_cpu_handler: Bad interrupt line %d\n", irq); + } + kvm_irq |= cs->cpu_index << KVM_ARM_IRQ_VCPU_SHIFT; + kvm_set_irq(kvm_state, kvm_irq, level ? 1 : 0); +#endif +} + +qemu_irq *arm_pic_init_cpu(ARMCPU *cpu) +{ + if (kvm_enabled()) { + return qemu_allocate_irqs(kvm_arm_pic_cpu_handler, cpu, 2); + } + return qemu_allocate_irqs(arm_pic_cpu_handler, cpu, 2); +} diff --git a/hw/arm/pxa2xx.c b/hw/arm/pxa2xx.c new file mode 100644 index 0000000000..c0f50c90fe --- /dev/null +++ b/hw/arm/pxa2xx.c @@ -0,0 +1,2291 @@ +/* + * 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/pxa.h" +#include "sysemu/sysemu.h" +#include "hw/serial.h" +#include "hw/i2c.h" +#include "hw/ssi.h" +#include "char/char.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, + .minimum_version_id_old = 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, + .minimum_version_id_old = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(cm_regs, PXA2xxState, 4), + VMSTATE_UINT32(clkcfg, PXA2xxState), + VMSTATE_UINT32(pmnc, PXA2xxState), + VMSTATE_END_OF_LIST() + } +}; + +static int pxa2xx_clkcfg_read(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t *value) +{ + PXA2xxState *s = (PXA2xxState *)ri->opaque; + *value = s->clkcfg; + return 0; +} + +static int 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__); + } + return 0; +} + +static int 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] & (1 << 31))) { /* CPDIS */ + cpu_interrupt(&s->cpu->env, CPU_INTERRUPT_HALT); + break; + } + /* Fall through. */ + + case 2: + /* Deep-Idle */ + cpu_interrupt(&s->cpu->env, CPU_INTERRUPT_HALT); + s->pm_regs[RCSR >> 2] |= 0x8; /* Set GPR */ + goto message; + + case 3: + s->cpu->env.uncached_cpsr = + ARM_CPU_MODE_SVC | CPSR_A | CPSR_F | CPSR_I; + s->cpu->env.cp15.c1_sys = 0; + s->cpu->env.cp15.c1_coproc = 0; + s->cpu->env.cp15.c2_base0 = 0; + s->cpu->env.cp15.c3 = 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(cpu_single_env, CPU_INTERRUPT_HALT); + + goto message; + + default: + message: + printf("%s: machine entered %s mode\n", __func__, + pwrmode[value & 7]); + } + + return 0; +} + +static int pxa2xx_cppmnc_read(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t *value) +{ + PXA2xxState *s = (PXA2xxState *)ri->opaque; + *value = s->pmnc; + return 0; +} + +static int pxa2xx_cppmnc_write(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t value) +{ + PXA2xxState *s = (PXA2xxState *)ri->opaque; + s->pmnc = value; + return 0; +} + +static int pxa2xx_cpccnt_read(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t *value) +{ + PXA2xxState *s = (PXA2xxState *)ri->opaque; + if (s->pmnc & 1) { + *value = qemu_get_clock_ns(vm_clock); + } else { + *value = 0; + } + 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, + .readfn = pxa2xx_cppmnc_read, .writefn = pxa2xx_cppmnc_write }, + { .name = "CPCCNT", .cp = 14, .crn = 1, .crm = 1, .opc1 = 0, .opc2 = 0, + .access = PL1_RW, + .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, + .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, + .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, + .minimum_version_id_old = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(mm_regs, PXA2xxState, 0x1a), + VMSTATE_END_OF_LIST() + } +}; + +/* Synchronous Serial Ports */ +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + qemu_irq irq; + int 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]; + int rx_level; + int rx_start; +} PXA2xxSSPState; + +#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 (1 << 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_save(QEMUFile *f, void *opaque) +{ + PXA2xxSSPState *s = (PXA2xxSSPState *) opaque; + int i; + + qemu_put_be32(f, s->enable); + + qemu_put_be32s(f, &s->sscr[0]); + qemu_put_be32s(f, &s->sscr[1]); + qemu_put_be32s(f, &s->sspsp); + qemu_put_be32s(f, &s->ssto); + qemu_put_be32s(f, &s->ssitr); + qemu_put_be32s(f, &s->sssr); + qemu_put_8s(f, &s->sstsa); + qemu_put_8s(f, &s->ssrsa); + qemu_put_8s(f, &s->ssacd); + + qemu_put_byte(f, s->rx_level); + for (i = 0; i < s->rx_level; i ++) + qemu_put_byte(f, s->rx_fifo[(s->rx_start + i) & 0xf]); +} + +static int pxa2xx_ssp_load(QEMUFile *f, void *opaque, int version_id) +{ + PXA2xxSSPState *s = (PXA2xxSSPState *) opaque; + int i; + + s->enable = qemu_get_be32(f); + + qemu_get_be32s(f, &s->sscr[0]); + qemu_get_be32s(f, &s->sscr[1]); + qemu_get_be32s(f, &s->sspsp); + qemu_get_be32s(f, &s->ssto); + qemu_get_be32s(f, &s->ssitr); + qemu_get_be32s(f, &s->sssr); + qemu_get_8s(f, &s->sstsa); + qemu_get_8s(f, &s->ssrsa); + qemu_get_8s(f, &s->ssacd); + + s->rx_level = qemu_get_byte(f); + s->rx_start = 0; + for (i = 0; i < s->rx_level; i ++) + s->rx_fifo[i] = qemu_get_byte(f); + + return 0; +} + +static int pxa2xx_ssp_init(SysBusDevice *dev) +{ + PXA2xxSSPState *s = FROM_SYSBUS(PXA2xxSSPState, dev); + + sysbus_init_irq(dev, &s->irq); + + memory_region_init_io(&s->iomem, &pxa2xx_ssp_ops, s, "pxa2xx-ssp", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + register_savevm(&dev->qdev, "pxa2xx_ssp", -1, 0, + pxa2xx_ssp_save, pxa2xx_ssp_load, s); + + s->bus = ssi_create_bus(&dev->qdev, "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 */ + +typedef struct { + SysBusDevice busdev; + 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_get_clock_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_get_clock_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_get_clock_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))) + qemu_mod_timer(s->rtc_hz, s->last_hz + + (((s->rtar - s->last_rcnr) * 1000 * + ((s->rttr & 0xffff) + 1)) >> 15)); + else + qemu_del_timer(s->rtc_hz); + + if ((rtsr & (1 << 5)) && !(rtsr & (1 << 4))) + qemu_mod_timer(s->rtc_rdal1, s->last_hz + + (((s->rdar1 - s->last_rdcr) * 1000 * + ((s->rttr & 0xffff) + 1)) >> 15)); /* TODO: fixup */ + else + qemu_del_timer(s->rtc_rdal1); + + if ((rtsr & (1 << 7)) && !(rtsr & (1 << 6))) + qemu_mod_timer(s->rtc_rdal2, s->last_hz + + (((s->rdar2 - s->last_rdcr) * 1000 * + ((s->rttr & 0xffff) + 1)) >> 15)); /* TODO: fixup */ + else + qemu_del_timer(s->rtc_rdal2); + + if ((rtsr & 0x1200) == 0x1200 && !(rtsr & (1 << 8))) + qemu_mod_timer(s->rtc_swal1, s->last_sw + + (s->swar1 - s->last_swcr) * 10); /* TODO: fixup */ + else + qemu_del_timer(s->rtc_swal1); + + if ((rtsr & 0x1800) == 0x1800 && !(rtsr & (1 << 10))) + qemu_mod_timer(s->rtc_swal2, s->last_sw + + (s->swar2 - s->last_swcr) * 10); /* TODO: fixup */ + else + qemu_del_timer(s->rtc_swal2); + + if ((rtsr & 0xc000) == 0xc000 && !(rtsr & (1 << 13))) + qemu_mod_timer(s->rtc_pi, s->last_pi + + (s->piar & 0xffff) - s->last_rtcpicr); + else + qemu_del_timer(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_get_clock_ms(rtc_clock) - s->last_hz) << 15) / + (1000 * ((s->rttr & 0xffff) + 1)); + case RDCR: + return s->last_rdcr + ((qemu_get_clock_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_get_clock_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 & (1 << 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 = FROM_SYSBUS(PXA2xxRTCState, 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_get_clock_ms(rtc_clock); + + s->rtc_hz = qemu_new_timer_ms(rtc_clock, pxa2xx_rtc_hz_tick, s); + s->rtc_rdal1 = qemu_new_timer_ms(rtc_clock, pxa2xx_rtc_rdal1_tick, s); + s->rtc_rdal2 = qemu_new_timer_ms(rtc_clock, pxa2xx_rtc_rdal2_tick, s); + s->rtc_swal1 = qemu_new_timer_ms(rtc_clock, pxa2xx_rtc_swal1_tick, s); + s->rtc_swal2 = qemu_new_timer_ms(rtc_clock, pxa2xx_rtc_swal2_tick, s); + s->rtc_pi = qemu_new_timer_ms(rtc_clock, pxa2xx_rtc_pi_tick, s); + + sysbus_init_irq(dev, &s->rtc_irq); + + memory_region_init_io(&s->iomem, &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, + .minimum_version_id_old = 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 = "pxa2xx_rtc", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PXA2xxRTCState), + .class_init = pxa2xx_rtc_sysbus_class_init, +}; + +/* I2C Interface */ +typedef struct { + I2CSlave i2c; + PXA2xxI2CState *host; +} PXA2xxI2CSlaveState; + +struct PXA2xxI2CState { + SysBusDevice busdev; + MemoryRegion iomem; + PXA2xxI2CSlaveState *slave; + i2c_bus *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 = FROM_I2C_SLAVE(PXA2xxI2CSlaveState, 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 = FROM_I2C_SLAVE(PXA2xxI2CSlaveState, 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 = FROM_I2C_SLAVE(PXA2xxI2CSlaveState, 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; + + addr -= s->offset; + switch (addr) { + case ICR: + return s->control; + case ISR: + return s->status | (i2c_bus_busy(s->bus) << 2); + case ISAR: + return s->slave->i2c.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(&s->slave->i2c, 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, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_I2C_SLAVE(i2c, PXA2xxI2CSlaveState), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_pxa2xx_i2c = { + .name = "pxa2xx_i2c", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 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 = "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; + + i2c_dev = SYS_BUS_DEVICE(qdev_create(NULL, "pxa2xx_i2c")); + qdev_prop_set_uint32(&i2c_dev->qdev, "size", region_size + 1); + qdev_prop_set_uint32(&i2c_dev->qdev, "offset", base & region_size); + + qdev_init_nofail(&i2c_dev->qdev); + + sysbus_mmio_map(i2c_dev, 0, base & ~region_size); + sysbus_connect_irq(i2c_dev, 0, irq); + + s = FROM_SYSBUS(PXA2xxI2CState, i2c_dev); + /* FIXME: Should the slave device really be on a separate bus? */ + dev = i2c_create_slave(i2c_init_bus(NULL, "dummy"), "pxa2xx-i2c-slave", 0); + s->slave = FROM_I2C_SLAVE(PXA2xxI2CSlaveState, I2C_SLAVE(dev)); + s->slave->host = s; + + return s; +} + +static int pxa2xx_i2c_initfn(SysBusDevice *dev) +{ + PXA2xxI2CState *s = FROM_SYSBUS(PXA2xxI2CState, dev); + + s->bus = i2c_init_bus(&dev->qdev, "i2c"); + + memory_region_init_io(&s->iomem, &pxa2xx_i2c_ops, s, + "pxa2xx-i2x", s->region_size); + sysbus_init_mmio(dev, &s->iomem); + sysbus_init_irq(dev, &s->irq); + + return 0; +} + +i2c_bus *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 = "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, + .minimum_version_id_old = 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, &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 */ +struct PXA2xxFIrState { + MemoryRegion iomem; + qemu_irq irq; + qemu_irq rx_dma; + qemu_irq tx_dma; + int enable; + CharDriverState *chr; + + uint8_t control[3]; + uint8_t status[2]; + + int rx_len; + int rx_start; + uint8_t rx_fifo[64]; +}; + +static void pxa2xx_fir_reset(PXA2xxFIrState *s) +{ + 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_save(QEMUFile *f, void *opaque) +{ + PXA2xxFIrState *s = (PXA2xxFIrState *) opaque; + int i; + + qemu_put_be32(f, s->enable); + + qemu_put_8s(f, &s->control[0]); + qemu_put_8s(f, &s->control[1]); + qemu_put_8s(f, &s->control[2]); + qemu_put_8s(f, &s->status[0]); + qemu_put_8s(f, &s->status[1]); + + qemu_put_byte(f, s->rx_len); + for (i = 0; i < s->rx_len; i ++) + qemu_put_byte(f, s->rx_fifo[(s->rx_start + i) & 63]); +} + +static int pxa2xx_fir_load(QEMUFile *f, void *opaque, int version_id) +{ + PXA2xxFIrState *s = (PXA2xxFIrState *) opaque; + int i; + + s->enable = qemu_get_be32(f); + + qemu_get_8s(f, &s->control[0]); + qemu_get_8s(f, &s->control[1]); + qemu_get_8s(f, &s->control[2]); + qemu_get_8s(f, &s->status[0]); + qemu_get_8s(f, &s->status[1]); + + s->rx_len = qemu_get_byte(f); + s->rx_start = 0; + for (i = 0; i < s->rx_len; i ++) + s->rx_fifo[i] = qemu_get_byte(f); + + return 0; +} + +static PXA2xxFIrState *pxa2xx_fir_init(MemoryRegion *sysmem, + hwaddr base, + qemu_irq irq, qemu_irq rx_dma, qemu_irq tx_dma, + CharDriverState *chr) +{ + PXA2xxFIrState *s = (PXA2xxFIrState *) + g_malloc0(sizeof(PXA2xxFIrState)); + + s->irq = irq; + s->rx_dma = rx_dma; + s->tx_dma = tx_dma; + s->chr = chr; + + pxa2xx_fir_reset(s); + + memory_region_init_io(&s->iomem, &pxa2xx_fir_ops, s, "pxa2xx-fir", 0x1000); + memory_region_add_subregion(sysmem, base, &s->iomem); + + if (chr) + qemu_chr_add_handlers(chr, pxa2xx_fir_is_empty, + pxa2xx_fir_rx, pxa2xx_fir_event, s); + + register_savevm(NULL, "pxa2xx_fir", 0, 0, pxa2xx_fir_save, + pxa2xx_fir_load, s); + + return s; +} + +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_irqs(pxa2xx_reset, s, 1)[0]; + + /* SDRAM & Internal Memory Storage */ + memory_region_init_ram(&s->sdram, "pxa270.sdram", sdram_size); + vmstate_register_ram_global(&s->sdram); + memory_region_add_subregion(address_space, PXA2XX_SDRAM_BASE, &s->sdram); + memory_region_init_ram(&s->internal, "pxa270.internal", 0x40000); + 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, dinfo->bdrv, + 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, &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, &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, &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("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(false)) { + 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("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_irqs(pxa2xx_reset, s, 1)[0]; + + /* SDRAM & Internal Memory Storage */ + memory_region_init_ram(&s->sdram, "pxa255.sdram", sdram_size); + vmstate_register_ram_global(&s->sdram); + memory_region_add_subregion(address_space, PXA2XX_SDRAM_BASE, &s->sdram); + memory_region_init_ram(&s->internal, "pxa255.internal", + PXA2XX_INTERNAL_SIZE); + 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, dinfo->bdrv, + 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, &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, &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, &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("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(false)) { + 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("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); + + sdc->init = pxa2xx_ssp_init; +} + +static const TypeInfo pxa2xx_ssp_info = { + .name = "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_init(pxa2xx_register_types) diff --git a/hw/arm/pxa2xx_gpio.c b/hw/arm/pxa2xx_gpio.c new file mode 100644 index 0000000000..eef8411e86 --- /dev/null +++ b/hw/arm/pxa2xx_gpio.c @@ -0,0 +1,350 @@ +/* + * 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/pxa.h" + +#define PXA2XX_GPIO_BANKS 4 + +typedef struct PXA2xxGPIOInfo PXA2xxGPIOInfo; +struct PXA2xxGPIOInfo { + SysBusDevice busdev; + 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 gpsr[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; + int bank; + uint32_t mask; + + if (line >= s->lines) { + printf("%s: No GPIO pin %i\n", __FUNCTION__, line); + return; + } + + bank = line >> 5; + mask = 1 << (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 (s->cpu->env.halted && (mask & ~s->dir[bank] & pxa2xx_gpio_wake[bank])) { + cpu_interrupt(&s->cpu->env, 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 = ffs(diff) - 1; + 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 */ + printf("%s: Read from a write-only register " REG_FMT "\n", + __FUNCTION__, offset); + return s->gpsr[bank]; /* Return last written value. */ + + case GPCR: /* GPIO Pin-Output Clear registers */ + printf("%s: Read from a write-only register " REG_FMT "\n", + __FUNCTION__, offset); + return 31337; /* Specified as unpredictable in the docs. */ + + 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); + s->gpsr[bank] = value; + 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, "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 *dev) +{ + PXA2xxGPIOInfo *s; + + s = FROM_SYSBUS(PXA2xxGPIOInfo, dev); + + s->cpu = ARM_CPU(qemu_get_cpu(s->ncpu)); + + qdev_init_gpio_in(&dev->qdev, pxa2xx_gpio_set, s->lines); + qdev_init_gpio_out(&dev->qdev, s->handler, s->lines); + + memory_region_init_io(&s->iomem, &pxa_gpio_ops, s, "pxa2xx-gpio", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + sysbus_init_irq(dev, &s->irq0); + sysbus_init_irq(dev, &s->irq1); + sysbus_init_irq(dev, &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 = FROM_SYSBUS(PXA2xxGPIOInfo, SYS_BUS_DEVICE(dev)); + s->read_notify = handler; +} + +static const VMStateDescription vmstate_pxa2xx_gpio_regs = { + .name = "pxa2xx-gpio", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_INT32(lines, PXA2xxGPIOInfo), + 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_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; +} + +static const TypeInfo pxa2xx_gpio_info = { + .name = "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/hw/arm/pxa2xx_pic.c b/hw/arm/pxa2xx_pic.c new file mode 100644 index 0000000000..145fc78c2f --- /dev/null +++ b/hw/arm/pxa2xx_pic.c @@ -0,0 +1,334 @@ +/* + * 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/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 + +typedef struct { + SysBusDevice busdev; + 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; + + if (s->cpu->env.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(&s->cpu->env, 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(&s->cpu->env, CPU_INTERRUPT_FIQ); + } else { + cpu_reset_interrupt(&s->cpu->env, CPU_INTERRUPT_FIQ); + } + + if ((mask[0] & ~s->is_fiq[0]) || (mask[1] & ~s->is_fiq[1])) { + cpu_interrupt(&s->cpu->env, CPU_INTERRUPT_HARD); + } else { + cpu_reset_interrupt(&s->cpu->env, 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] & (1 << 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 |= (1 << 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 int pxa2xx_pic_cp_read(CPUARMState *env, const ARMCPRegInfo *ri, + uint64_t *value) +{ + int offset = pxa2xx_cp_reg_map[ri->crn]; + *value = pxa2xx_pic_mem_read(ri->opaque, offset, 4); + return 0; +} + +static int 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); + return 0; +} + +#define REGINFO_FOR_PIC_CP(NAME, CRN) \ + { .name = NAME, .cp = 6, .crn = CRN, .crm = 0, .opc1 = 0, .opc2 = 0, \ + .access = PL1_RW, \ + .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) +{ + CPUARMState *env = &cpu->env; + DeviceState *dev = qdev_create(NULL, "pxa2xx_pic"); + PXA2xxPICState *s = FROM_SYSBUS(PXA2xxPICState, SYS_BUS_DEVICE(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, &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(arm_env_get_cpu(env), pxa_pic_cp_reginfo, s); + + return dev; +} + +static VMStateDescription vmstate_pxa2xx_pic_regs = { + .name = "pxa2xx_pic", + .version_id = 0, + .minimum_version_id = 0, + .minimum_version_id_old = 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 = "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/hw/arm/realview.c b/hw/arm/realview.c new file mode 100644 index 0000000000..5fb490c832 --- /dev/null +++ b/hw/arm/realview.c @@ -0,0 +1,404 @@ +/* + * 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-misc.h" +#include "hw/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.h" +#include "sysemu/blockdev.h" +#include "exec/address-spaces.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(QEMUMachineInitArgs *args, + enum realview_board_type board_type) +{ + ARMCPU *cpu = NULL; + CPUARMState *env; + MemoryRegion *sysmem = get_system_memory(); + MemoryRegion *ram_lo = g_new(MemoryRegion, 1); + 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 *irqp; + qemu_irq pic[64]; + qemu_irq mmc_irq[2]; + PCIBus *pci_bus; + NICInfo *nd; + i2c_bus *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 = args->ram_size; + + switch (board_type) { + case BOARD_EB: + break; + case BOARD_EB_MPCORE: + is_mpcore = 1; + break; + case BOARD_PB_A8: + is_pb = 1; + break; + case BOARD_PBX_A9: + is_mpcore = 1; + is_pb = 1; + break; + } + for (n = 0; n < smp_cpus; n++) { + cpu = cpu_arm_init(args->cpu_model); + if (!cpu) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + irqp = arm_pic_init_cpu(cpu); + cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ]; + } + 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. */ + low_ram_size = ram_size - 0x20000000; + ram_size = 0x20000000; + memory_region_init_ram(ram_lo, "realview.lowmem", low_ram_size); + vmstate_register_ram_global(ram_lo); + memory_region_add_subregion(sysmem, 0x20000000, ram_lo); + } + + memory_region_init_ram(ram_hi, "realview.highmem", ram_size); + 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, "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) { + hwaddr periphbase; + 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); + if (is_pb) { + periphbase = 0x1f000000; + } else { + periphbase = 0x10100000; + } + 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, 0x61000000); /* PCI self-config */ + sysbus_mmio_map(busdev, 1, 0x62000000); /* PCI config */ + sysbus_mmio_map(busdev, 2, 0x63000000); /* PCI I/O */ + 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(false)) { + 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 { + pci_nic_init_nofail(nd, "rtl8139", NULL); + } + } + + dev = sysbus_create_simple("versatile_i2c", 0x10002000, NULL); + i2c = (i2c_bus *)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. */ + /* 0x61000000 PCI Self Config. */ + /* 0x62000000 PCI Config. */ + /* 0x63000000 PCI IO. */ + /* 0x64000000 PCI mem 0. */ + /* 0x68000000 PCI mem 1. */ + /* 0x6c000000 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, "realview.hack", 0x1000); + 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 = args->kernel_filename; + realview_binfo.kernel_cmdline = args->kernel_cmdline; + realview_binfo.initrd_filename = args->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_env_get_cpu(first_cpu), &realview_binfo); +} + +static void realview_eb_init(QEMUMachineInitArgs *args) +{ + if (!args->cpu_model) { + args->cpu_model = "arm926"; + } + realview_init(args, BOARD_EB); +} + +static void realview_eb_mpcore_init(QEMUMachineInitArgs *args) +{ + if (!args->cpu_model) { + args->cpu_model = "arm11mpcore"; + } + realview_init(args, BOARD_EB_MPCORE); +} + +static void realview_pb_a8_init(QEMUMachineInitArgs *args) +{ + if (!args->cpu_model) { + args->cpu_model = "cortex-a8"; + } + realview_init(args, BOARD_PB_A8); +} + +static void realview_pbx_a9_init(QEMUMachineInitArgs *args) +{ + if (!args->cpu_model) { + args->cpu_model = "cortex-a9"; + } + realview_init(args, 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, + DEFAULT_MACHINE_OPTIONS, +}; + +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, + DEFAULT_MACHINE_OPTIONS, +}; + +static QEMUMachine realview_pb_a8_machine = { + .name = "realview-pb-a8", + .desc = "ARM RealView Platform Baseboard for Cortex-A8", + .init = realview_pb_a8_init, + DEFAULT_MACHINE_OPTIONS, +}; + +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, + DEFAULT_MACHINE_OPTIONS, +}; + +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/hw/arm/spitz.c b/hw/arm/spitz.c new file mode 100644 index 0000000000..f5832bea93 --- /dev/null +++ b/hw/arm/spitz.c @@ -0,0 +1,1138 @@ +/* + * 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/pxa.h" +#include "hw/arm-misc.h" +#include "sysemu/sysemu.h" +#include "hw/pcmcia.h" +#include "hw/i2c.h" +#include "hw/ssi.h" +#include "hw/flash.h" +#include "qemu/timer.h" +#include "hw/devices.h" +#include "hw/sharpsl.h" +#include "ui/console.h" +#include "block/block.h" +#include "audio/audio.h" +#include "hw/boards.h" +#include "sysemu/blockdev.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) + +typedef struct { + SysBusDevice busdev; + 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, "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; + DriveInfo *nand; + + s = FROM_SYSBUS(SLNANDState, dev); + + s->ctl = 0; + nand = drive_get(IF_MTD, 0, 0); + s->nand = nand_init(nand ? nand->bdrv : NULL, s->manf_id, s->chip_id); + + memory_region_init_io(&s->iomem, &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, +}; + +typedef struct { + SysBusDevice busdev; + 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 SHIFT (1 << 7) +#define CTRL (1 << 8) +#define 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 | SHIFT) : + (keycode & ~SHIFT))]; + + if (code != mapcode) { +#if 0 + if ((code & SHIFT) && !(s->modifiers & 1)) + QUEUE_KEY(0x2a | (keycode & 0x80)); + if ((code & CTRL ) && !(s->modifiers & 4)) + QUEUE_KEY(0x1d | (keycode & 0x80)); + if ((code & FN ) && !(s->modifiers & 8)) + QUEUE_KEY(0x38 | (keycode & 0x80)); + if ((code & FN ) && (s->modifiers & 1)) + QUEUE_KEY(0x2a | (~keycode & 0x80)); + if ((code & 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 & SHIFT) && !((s->modifiers | s->imodifiers) & 1)) { + QUEUE_KEY(0x2a); + s->imodifiers |= 1; + } + if ((code & CTRL ) && !((s->modifiers | s->imodifiers) & 4)) { + QUEUE_KEY(0x1d); + s->imodifiers |= 4; + } + if ((code & FN ) && !((s->modifiers | s->imodifiers) & 8)) { + QUEUE_KEY(0x38); + s->imodifiers |= 8; + } + if ((code & FN ) && (s->modifiers & 1) && + !(s->imodifiers & 0x10)) { + QUEUE_KEY(0x2a | 0x80); + s->imodifiers |= 0x10; + } + if ((code & 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; + } + + qemu_mod_timer(s->kbdtimer, qemu_get_clock_ns(vm_clock) + + 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 | SHIFT ] = 0x02 | SHIFT; /* exclam */ + s->pre_map[0x28 | SHIFT ] = 0x03 | SHIFT; /* quotedbl */ + s->pre_map[0x04 | SHIFT ] = 0x04 | SHIFT; /* numbersign */ + s->pre_map[0x05 | SHIFT ] = 0x05 | SHIFT; /* dollar */ + s->pre_map[0x06 | SHIFT ] = 0x06 | SHIFT; /* percent */ + s->pre_map[0x08 | SHIFT ] = 0x07 | SHIFT; /* ampersand */ + s->pre_map[0x28 ] = 0x08 | SHIFT; /* apostrophe */ + s->pre_map[0x0a | SHIFT ] = 0x09 | SHIFT; /* parenleft */ + s->pre_map[0x0b | SHIFT ] = 0x0a | SHIFT; /* parenright */ + s->pre_map[0x29 | SHIFT ] = 0x0b | SHIFT; /* asciitilde */ + s->pre_map[0x03 | SHIFT ] = 0x0c | SHIFT; /* at */ + s->pre_map[0xd3 ] = 0x0e | FN; /* Delete */ + s->pre_map[0x3a ] = 0x0f | FN; /* Caps_Lock */ + s->pre_map[0x07 | SHIFT ] = 0x11 | FN; /* asciicircum */ + s->pre_map[0x0d ] = 0x12 | FN; /* equal */ + s->pre_map[0x0d | SHIFT ] = 0x13 | FN; /* plus */ + s->pre_map[0x1a ] = 0x14 | FN; /* bracketleft */ + s->pre_map[0x1b ] = 0x15 | FN; /* bracketright */ + s->pre_map[0x1a | SHIFT ] = 0x16 | FN; /* braceleft */ + s->pre_map[0x1b | SHIFT ] = 0x17 | FN; /* braceright */ + s->pre_map[0x27 ] = 0x22 | FN; /* semicolon */ + s->pre_map[0x27 | SHIFT ] = 0x23 | FN; /* colon */ + s->pre_map[0x09 | SHIFT ] = 0x24 | FN; /* asterisk */ + s->pre_map[0x2b ] = 0x25 | FN; /* backslash */ + s->pre_map[0x2b | SHIFT ] = 0x26 | FN; /* bar */ + s->pre_map[0x0c | SHIFT ] = 0x30 | FN; /* underscore */ + s->pre_map[0x33 | SHIFT ] = 0x33 | FN; /* less */ + s->pre_map[0x35 ] = 0x33 | SHIFT; /* slash */ + s->pre_map[0x34 | SHIFT ] = 0x34 | FN; /* greater */ + s->pre_map[0x35 | SHIFT ] = 0x34 | SHIFT; /* question */ + s->pre_map[0x49 ] = 0x48 | FN; /* Page_Up */ + s->pre_map[0x51 ] = 0x50 | FN; /* Page_Down */ + + s->modifiers = 0; + s->imodifiers = 0; + s->fifopos = 0; + s->fifolen = 0; +} + +#undef SHIFT +#undef CTRL +#undef 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("spitz-keyboard", -1, NULL); + s = FROM_SYSBUS(SpitzKeyboardState, SYS_BUS_DEVICE(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)); + + qemu_mod_timer(s->kbdtimer, qemu_get_clock_ns(vm_clock)); + + qemu_add_kbd_event_handler(spitz_keyboard_handler, s); +} + +static int spitz_keyboard_init(SysBusDevice *dev) +{ + SpitzKeyboardState *s; + int i, j; + + s = FROM_SYSBUS(SpitzKeyboardState, dev); + + 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 = qemu_new_timer_ns(vm_clock, spitz_keyboard_tick, s); + qdev_init_gpio_in(&dev->qdev, spitz_keyboard_strobe, SPITZ_KEY_STROBE_NUM); + qdev_init_gpio_out(&dev->qdev, 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 *dev) +{ + CorgiSSPState *s = FROM_SSI_SLAVE(CorgiSSPState, dev); + + qdev_init_gpio_in(&dev->qdev, corgi_ssp_gpio_cs, 3); + s->bus[0] = ssi_create_bus(&dev->qdev, "ssi0"); + s->bus[1] = ssi_create_bus(&dev->qdev, "ssi1"); + s->bus[2] = ssi_create_bus(&dev->qdev, "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. */ + i2c_bus *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_irqs(spitz_wm8750_addr, wm, 1)[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_irqs(spitz_lcd_hsync_handler, cpu, 1)[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(QEMUMachineInitArgs *args, + 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 = args->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, "spitz.rom", SPITZ_ROM); + 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 = args->kernel_filename; + spitz_binfo.kernel_cmdline = args->kernel_cmdline; + spitz_binfo.initrd_filename = args->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(QEMUMachineInitArgs *args) +{ + spitz_common_init(args, spitz, 0x2c9); +} + +static void borzoi_init(QEMUMachineInitArgs *args) +{ + spitz_common_init(args, borzoi, 0x33f); +} + +static void akita_init(QEMUMachineInitArgs *args) +{ + spitz_common_init(args, akita, 0x2e8); +} + +static void terrier_init(QEMUMachineInitArgs *args) +{ + spitz_common_init(args, terrier, 0x33f); +} + +static QEMUMachine akitapda_machine = { + .name = "akita", + .desc = "Akita PDA (PXA270)", + .init = akita_init, + DEFAULT_MACHINE_OPTIONS, +}; + +static QEMUMachine spitzpda_machine = { + .name = "spitz", + .desc = "Spitz PDA (PXA270)", + .init = spitz_init, + DEFAULT_MACHINE_OPTIONS, +}; + +static QEMUMachine borzoipda_machine = { + .name = "borzoi", + .desc = "Borzoi PDA (PXA270)", + .init = borzoi_init, + DEFAULT_MACHINE_OPTIONS, +}; + +static QEMUMachine terrierpda_machine = { + .name = "terrier", + .desc = "Terrier PDA (PXA270)", + .init = terrier_init, + DEFAULT_MACHINE_OPTIONS, +}; + +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, + .minimum_version_id_old = 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; +} + +static const TypeInfo sl_nand_info = { + .name = "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, + .minimum_version_id_old = 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 = "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, + .minimum_version_id_old = 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, + .minimum_version_id_old = 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/hw/arm/stellaris.c b/hw/arm/stellaris.c new file mode 100644 index 0000000000..f4ce7945f3 --- /dev/null +++ b/hw/arm/stellaris.c @@ -0,0 +1,1401 @@ +/* + * 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-misc.h" +#include "hw/devices.h" +#include "qemu/timer.h" +#include "hw/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 + +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. */ + +typedef struct gptm_state { + SysBusDevice busdev; + 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) +{ + qemu_del_timer(s->timer[n]); +} + +static void gptm_reload(gptm_state *s, int n, int reset) +{ + int64_t tick; + if (reset) + tick = qemu_get_clock_ns(vm_clock); + else + tick = s->tick[n]; + + if (s->config == 0) { + /* 32-bit CountDown. */ + uint32_t count; + count = s->load[0] | (s->load[1] << 16); + tick += (int64_t)count * 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; + qemu_mod_timer(s->timer[n], tick); +} + +static void gptm_tick(void *opaque) +{ + gptm_state **p = (gptm_state **)opaque; + gptm_state *s; + int n; + + s = *p; + n = p - s->opaque; + if (s->config == 0) { + s->state |= 1; + if ((s->control & 0x20)) { + /* Output trigger. */ + qemu_irq_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->control == 1) + return s->rtc; + case 0x4c: /* TBR */ + hw_error("TODO: Timer value read\n"); + default: + hw_error("gptm_read: 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, + .minimum_version_id_old = 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_ARRAY(timer, gptm_state, 2), + VMSTATE_END_OF_LIST() + } +}; + +static int stellaris_gptm_init(SysBusDevice *dev) +{ + gptm_state *s = FROM_SYSBUS(gptm_state, dev); + + sysbus_init_irq(dev, &s->irq); + qdev_init_gpio_out(&dev->qdev, &s->trigger, 1); + + memory_region_init_io(&s->iomem, &gptm_ops, s, + "gptm", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + + s->opaque[0] = s->opaque[1] = s; + s->timer[0] = qemu_new_timer_ns(vm_clock, gptm_tick, &s->opaque[0]); + s->timer[1] = qemu_new_timer_ns(vm_clock, gptm_tick, &s->opaque[1]); + vmstate_register(&dev->qdev, -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, + .minimum_version_id_old = 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, &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. */ + +typedef struct { + SysBusDevice busdev; + i2c_bus *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, + .minimum_version_id_old = 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 * dev) +{ + stellaris_i2c_state *s = FROM_SYSBUS(stellaris_i2c_state, dev); + i2c_bus *bus; + + sysbus_init_irq(dev, &s->irq); + bus = i2c_init_bus(&dev->qdev, "i2c"); + s->bus = bus; + + memory_region_init_io(&s->iomem, &stellaris_i2c_ops, s, + "i2c", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + /* ??? For now we only implement the master interface. */ + stellaris_i2c_reset(s); + vmstate_register(&dev->qdev, -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 + +typedef struct +{ + SysBusDevice busdev; + 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, + .minimum_version_id_old = 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 *dev) +{ + stellaris_adc_state *s = FROM_SYSBUS(stellaris_adc_state, dev); + int n; + + for (n = 0; n < 4; n++) { + sysbus_init_irq(dev, &s->irq[n]); + } + + memory_region_init_io(&s->iomem, &stellaris_adc_ops, s, + "adc", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + stellaris_adc_reset(s); + qdev_init_gpio_in(&dev->qdev, stellaris_adc_trigger, 1); + vmstate_register(&dev->qdev, -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}; + + MemoryRegion *address_space_mem = get_system_memory(); + 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; + i2c_bus *i2c; + DeviceState *dev; + int i; + int j; + + flash_size = ((board->dc0 & 0xffff) + 1) << 1; + sram_size = (board->dc0 >> 18) + 1; + pic = armv7m_init(address_space_mem, + flash_size, sram_size, kernel_filename, cpu_model); + + if (board->dc1 & (1 << 16)) { + dev = sysbus_create_varargs("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("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("stellaris-i2c", 0x40020000, pic[8]); + i2c = (i2c_bus *)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(sddev, 0), + qdev_get_gpio_in(ssddev, 0)); + gpio_out[GPIO_C][7] = qdev_get_gpio_in(ssddev, 1); + + /* 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(QEMUMachineInitArgs *args) +{ + const char *cpu_model = args->cpu_model; + const char *kernel_filename = args->kernel_filename; + stellaris_init(kernel_filename, cpu_model, &stellaris_boards[0]); +} + +static void lm3s6965evb_init(QEMUMachineInitArgs *args) +{ + const char *cpu_model = args->cpu_model; + const char *kernel_filename = args->kernel_filename; + stellaris_init(kernel_filename, cpu_model, &stellaris_boards[1]); +} + +static QEMUMachine lm3s811evb_machine = { + .name = "lm3s811evb", + .desc = "Stellaris LM3S811EVB", + .init = lm3s811evb_init, + DEFAULT_MACHINE_OPTIONS, +}; + +static QEMUMachine lm3s6965evb_machine = { + .name = "lm3s6965evb", + .desc = "Stellaris LM3S6965EVB", + .init = lm3s6965evb_init, + DEFAULT_MACHINE_OPTIONS, +}; + +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 = "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 = "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 = "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/hw/arm/tosa.c b/hw/arm/tosa.c new file mode 100644 index 0000000000..747888c64e --- /dev/null +++ b/hw/arm/tosa.c @@ -0,0 +1,302 @@ +/* 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/pxa.h" +#include "hw/arm-misc.h" +#include "hw/devices.h" +#include "hw/sharpsl.h" +#include "hw/pcmcia.h" +#include "block/block.h" +#include "hw/boards.h" +#include "hw/i2c.h" +#include "hw/ssi.h" +#include "sysemu/blockdev.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; +} + +typedef struct { + I2CSlave i2c; + int len; + char buf[3]; +} TosaDACState; + +static int tosa_dac_send(I2CSlave *i2c, uint8_t data) +{ + TosaDACState *s = FROM_I2C_SLAVE(TosaDACState, 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 = FROM_I2C_SLAVE(TosaDACState, 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) +{ + i2c_bus *bus = pxa2xx_i2c_bus(cpu->i2c[0]); + i2c_create_slave(bus, "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(QEMUMachineInitArgs *args) +{ + const char *cpu_model = args->cpu_model; + const char *kernel_filename = args->kernel_filename; + const char *kernel_cmdline = args->kernel_cmdline; + const char *initrd_filename = args->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, "tosa.rom", TOSA_ROM); + 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, + DEFAULT_MACHINE_OPTIONS, +}; + +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 = "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/hw/arm/versatilepb.c b/hw/arm/versatilepb.c new file mode 100644 index 0000000000..baaa265888 --- /dev/null +++ b/hw/arm/versatilepb.c @@ -0,0 +1,403 @@ +/* + * 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-misc.h" +#include "hw/devices.h" +#include "net/net.h" +#include "sysemu/sysemu.h" +#include "hw/pci/pci.h" +#include "hw/i2c.h" +#include "hw/boards.h" +#include "sysemu/blockdev.h" +#include "exec/address-spaces.h" +#include "hw/flash.h" + +#define VERSATILE_FLASH_ADDR 0x34000000 +#define VERSATILE_FLASH_SIZE (64 * 1024 * 1024) +#define VERSATILE_FLASH_SECT_SIZE (256 * 1024) + +/* Primary interrupt controller. */ + +typedef struct vpb_sic_state +{ + SysBusDevice busdev; + 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 *dev) +{ + vpb_sic_state *s = FROM_SYSBUS(vpb_sic_state, dev); + int i; + + qdev_init_gpio_in(&dev->qdev, vpb_sic_set_irq, 32); + for (i = 0; i < 32; i++) { + sysbus_init_irq(dev, &s->parent[i]); + } + s->irq = 31; + memory_region_init_io(&s->iomem, &vpb_sic_ops, s, "vpb-sic", 0x1000); + sysbus_init_mmio(dev, &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(QEMUMachineInitArgs *args, int board_id) +{ + ARMCPU *cpu; + MemoryRegion *sysmem = get_system_memory(); + MemoryRegion *ram = g_new(MemoryRegion, 1); + qemu_irq *cpu_pic; + qemu_irq pic[32]; + qemu_irq sic[32]; + DeviceState *dev, *sysctl; + SysBusDevice *busdev; + DeviceState *pl041; + PCIBus *pci_bus; + NICInfo *nd; + i2c_bus *i2c; + int n; + int done_smc = 0; + DriveInfo *dinfo; + + if (!args->cpu_model) { + args->cpu_model = "arm926"; + } + cpu = cpu_arm_init(args->cpu_model); + if (!cpu) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + memory_region_init_ram(ram, "versatile.ram", args->ram_size); + vmstate_register_ram_global(ram); + /* ??? 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); + + cpu_pic = arm_pic_init_cpu(cpu); + dev = sysbus_create_varargs("pl190", 0x10140000, + cpu_pic[ARM_PIC_CPU_IRQ], + cpu_pic[ARM_PIC_CPU_FIQ], NULL); + for (n = 0; n < 32; n++) { + pic[n] = qdev_get_gpio_in(dev, n); + } + dev = sysbus_create_simple("versatilepb_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, 0x41000000); /* PCI self-config */ + sysbus_mmio_map(busdev, 1, 0x42000000); /* PCI config */ + 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"); + + /* The Versatile PCI bridge does not provide access to PCI IO space, + so many of the qemu PCI devices are not useable. */ + 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, "rtl8139", NULL); + } + } + if (usb_enabled(false)) { + 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 = (i2c_bus *)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 ? dinfo->bdrv : 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 = args->ram_size; + versatile_binfo.kernel_filename = args->kernel_filename; + versatile_binfo.kernel_cmdline = args->kernel_cmdline; + versatile_binfo.initrd_filename = args->initrd_filename; + versatile_binfo.board_id = board_id; + arm_load_kernel(cpu, &versatile_binfo); +} + +static void vpb_init(QEMUMachineInitArgs *args) +{ + versatile_init(args, 0x183); +} + +static void vab_init(QEMUMachineInitArgs *args) +{ + versatile_init(args, 0x25e); +} + +static QEMUMachine versatilepb_machine = { + .name = "versatilepb", + .desc = "ARM Versatile/PB (ARM926EJ-S)", + .init = vpb_init, + .block_default_type = IF_SCSI, + DEFAULT_MACHINE_OPTIONS, +}; + +static QEMUMachine versatileab_machine = { + .name = "versatileab", + .desc = "ARM Versatile/AB (ARM926EJ-S)", + .init = vab_init, + .block_default_type = IF_SCSI, + DEFAULT_MACHINE_OPTIONS, +}; + +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->no_user = 1; + dc->vmsd = &vmstate_vpb_sic; +} + +static const TypeInfo vpb_sic_info = { + .name = "versatilepb_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/hw/arm/vexpress.c b/hw/arm/vexpress.c new file mode 100644 index 0000000000..02922c38b3 --- /dev/null +++ b/hw/arm/vexpress.c @@ -0,0 +1,500 @@ +/* + * 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-misc.h" +#include "hw/primecell.h" +#include "hw/devices.h" +#include "net/net.h" +#include "sysemu/sysemu.h" +#include "hw/boards.h" +#include "exec/address-spaces.h" +#include "sysemu/blockdev.h" +#include "hw/flash.h" + +#define VEXPRESS_BOARD_ID 0x8e0 +#define VEXPRESS_FLASH_SIZE (64 * 1024 * 1024) +#define VEXPRESS_FLASH_SECT_SIZE (256 * 1024) + +static struct arm_boot_info vexpress_binfo; + +/* 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_SRAM, + VE_VIDEORAM, + VE_ETHERNET, + VE_USB, + VE_DAPROM, +}; + +static hwaddr motherboard_legacy_map[] = { + /* 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_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[] = { + /* 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_SERIALDVI] = 0x1c160000, + [VE_RTC] = 0x1c170000, + [VE_COMPACTFLASH] = 0x1c1a0000, + [VE_CLCD] = 0x1c1f0000, +}; + +/* Structure defining the peculiarities of a specific daughterboard */ + +typedef struct VEDBoardInfo VEDBoardInfo; + +typedef void DBoardInitFn(const VEDBoardInfo *daughterboard, + ram_addr_t ram_size, + const char *cpu_model, + qemu_irq *pic, uint32_t *proc_id); + +struct VEDBoardInfo { + const hwaddr *motherboard_map; + hwaddr loader_start; + const hwaddr gic_cpu_if_addr; + DBoardInitFn *init; +}; + +static void a9_daughterboard_init(const VEDBoardInfo *daughterboard, + ram_addr_t ram_size, + const char *cpu_model, + qemu_irq *pic, uint32_t *proc_id) +{ + MemoryRegion *sysmem = get_system_memory(); + MemoryRegion *ram = g_new(MemoryRegion, 1); + MemoryRegion *lowram = g_new(MemoryRegion, 1); + DeviceState *dev; + SysBusDevice *busdev; + qemu_irq *irqp; + int n; + qemu_irq cpu_irq[4]; + ram_addr_t low_ram_size; + + if (!cpu_model) { + cpu_model = "cortex-a9"; + } + + *proc_id = 0x0c000191; + + for (n = 0; n < smp_cpus; n++) { + ARMCPU *cpu = cpu_arm_init(cpu_model); + if (!cpu) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + irqp = arm_pic_init_cpu(cpu); + cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ]; + } + + 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_init_ram(ram, "vexpress.highmem", ram_size); + vmstate_register_ram_global(ram); + 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, "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 */ + dev = qdev_create(NULL, "a9mpcore_priv"); + qdev_prop_set_uint32(dev, "num-cpu", smp_cpus); + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(busdev, 0, 0x1e000000); + for (n = 0; n < smp_cpus; n++) { + sysbus_connect_irq(busdev, n, cpu_irq[n]); + } + /* 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 the + * A9MP has internal interrupts 0..31). + */ + for (n = 0; n < 64; n++) { + pic[n] = qdev_get_gpio_in(dev, n); + } + + /* 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); +} + +static const VEDBoardInfo a9_daughterboard = { + .motherboard_map = motherboard_legacy_map, + .loader_start = 0x60000000, + .gic_cpu_if_addr = 0x1e000100, + .init = a9_daughterboard_init, +}; + +static void a15_daughterboard_init(const VEDBoardInfo *daughterboard, + ram_addr_t ram_size, + const char *cpu_model, + qemu_irq *pic, uint32_t *proc_id) +{ + int n; + MemoryRegion *sysmem = get_system_memory(); + MemoryRegion *ram = g_new(MemoryRegion, 1); + MemoryRegion *sram = g_new(MemoryRegion, 1); + qemu_irq cpu_irq[4]; + DeviceState *dev; + SysBusDevice *busdev; + + if (!cpu_model) { + cpu_model = "cortex-a15"; + } + + *proc_id = 0x14000237; + + for (n = 0; n < smp_cpus; n++) { + ARMCPU *cpu; + qemu_irq *irqp; + + cpu = cpu_arm_init(cpu_model); + if (!cpu) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + irqp = arm_pic_init_cpu(cpu); + cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ]; + } + + { + /* 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_init_ram(ram, "vexpress.highmem", ram_size); + vmstate_register_ram_global(ram); + /* 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) */ + dev = qdev_create(NULL, "a15mpcore_priv"); + qdev_prop_set_uint32(dev, "num-cpu", smp_cpus); + qdev_init_nofail(dev); + busdev = SYS_BUS_DEVICE(dev); + sysbus_mmio_map(busdev, 0, 0x2c000000); + for (n = 0; n < smp_cpus; n++) { + sysbus_connect_irq(busdev, n, cpu_irq[n]); + } + /* 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); + } + + /* 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, "vexpress.a15sram", 0x10000); + 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 VEDBoardInfo a15_daughterboard = { + .motherboard_map = motherboard_aseries_map, + .loader_start = 0x80000000, + .gic_cpu_if_addr = 0x2c002000, + .init = a15_daughterboard_init, +}; + +static void vexpress_common_init(const VEDBoardInfo *daughterboard, + QEMUMachineInitArgs *args) +{ + DeviceState *dev, *sysctl, *pl041; + qemu_irq pic[64]; + uint32_t proc_id; + uint32_t sys_id; + DriveInfo *dinfo; + ram_addr_t vram_size, sram_size; + MemoryRegion *sysmem = get_system_memory(); + MemoryRegion *vram = g_new(MemoryRegion, 1); + MemoryRegion *sram = g_new(MemoryRegion, 1); + const hwaddr *map = daughterboard->motherboard_map; + + daughterboard->init(daughterboard, args->ram_size, args->cpu_model, + pic, &proc_id); + + /* 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", proc_id); + 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); + if (!pflash_cfi01_register(map[VE_NORFLASH0], NULL, "vexpress.flash0", + VEXPRESS_FLASH_SIZE, dinfo ? dinfo->bdrv : NULL, + VEXPRESS_FLASH_SECT_SIZE, + VEXPRESS_FLASH_SIZE / VEXPRESS_FLASH_SECT_SIZE, 4, + 0x00, 0x89, 0x00, 0x18, 0)) { + fprintf(stderr, "vexpress: error registering flash 0.\n"); + exit(1); + } + + dinfo = drive_get_next(IF_PFLASH); + if (!pflash_cfi01_register(map[VE_NORFLASH1], NULL, "vexpress.flash1", + VEXPRESS_FLASH_SIZE, dinfo ? dinfo->bdrv : NULL, + VEXPRESS_FLASH_SECT_SIZE, + VEXPRESS_FLASH_SIZE / VEXPRESS_FLASH_SECT_SIZE, 4, + 0x00, 0x89, 0x00, 0x18, 0)) { + fprintf(stderr, "vexpress: error registering flash 1.\n"); + exit(1); + } + + sram_size = 0x2000000; + memory_region_init_ram(sram, "vexpress.sram", sram_size); + vmstate_register_ram_global(sram); + memory_region_add_subregion(sysmem, map[VE_SRAM], sram); + + vram_size = 0x800000; + memory_region_init_ram(vram, "vexpress.vram", vram_size); + 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 */ + + vexpress_binfo.ram_size = args->ram_size; + vexpress_binfo.kernel_filename = args->kernel_filename; + vexpress_binfo.kernel_cmdline = args->kernel_cmdline; + vexpress_binfo.initrd_filename = args->initrd_filename; + vexpress_binfo.nb_cpus = smp_cpus; + vexpress_binfo.board_id = VEXPRESS_BOARD_ID; + vexpress_binfo.loader_start = daughterboard->loader_start; + vexpress_binfo.smp_loader_start = map[VE_SRAM]; + vexpress_binfo.smp_bootreg_addr = map[VE_SYSREGS] + 0x30; + vexpress_binfo.gic_cpu_if_addr = daughterboard->gic_cpu_if_addr; + arm_load_kernel(arm_env_get_cpu(first_cpu), &vexpress_binfo); +} + +static void vexpress_a9_init(QEMUMachineInitArgs *args) +{ + vexpress_common_init(&a9_daughterboard, args); +} + +static void vexpress_a15_init(QEMUMachineInitArgs *args) +{ + vexpress_common_init(&a15_daughterboard, args); +} + +static QEMUMachine vexpress_a9_machine = { + .name = "vexpress-a9", + .desc = "ARM Versatile Express for Cortex-A9", + .init = vexpress_a9_init, + .block_default_type = IF_SCSI, + .max_cpus = 4, + DEFAULT_MACHINE_OPTIONS, +}; + +static QEMUMachine vexpress_a15_machine = { + .name = "vexpress-a15", + .desc = "ARM Versatile Express for Cortex-A15", + .init = vexpress_a15_init, + .block_default_type = IF_SCSI, + .max_cpus = 4, + DEFAULT_MACHINE_OPTIONS, +}; + +static void vexpress_machine_init(void) +{ + qemu_register_machine(&vexpress_a9_machine); + qemu_register_machine(&vexpress_a15_machine); +} + +machine_init(vexpress_machine_init); diff --git a/hw/arm/xilinx_zynq.c b/hw/arm/xilinx_zynq.c new file mode 100644 index 0000000000..f78c47e43e --- /dev/null +++ b/hw/arm/xilinx_zynq.c @@ -0,0 +1,224 @@ +/* + * 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-misc.h" +#include "net/net.h" +#include "exec/address-spaces.h" +#include "sysemu/sysemu.h" +#include "hw/boards.h" +#include "hw/flash.h" +#include "sysemu/blockdev.h" +#include "hw/loader.h" +#include "hw/ssi.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 */ + +static struct arm_boot_info zynq_binfo = {}; + +static void gem_init(NICInfo *nd, uint32_t base, qemu_irq irq) +{ + DeviceState *dev; + SysBusDevice *s; + + qemu_check_nic_model(nd, "cadence_gem"); + dev = qdev_create(NULL, "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, "xilinx,spips"); + 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_no_init(spi, "n25q128"); + qdev_init_nofail(flash_dev); + + cs_line = qdev_get_gpio_in(flash_dev, 0); + sysbus_connect_irq(busdev, i * num_ss + j + 1, cs_line); + } + } + +} + +static void zynq_init(QEMUMachineInitArgs *args) +{ + ram_addr_t ram_size = args->ram_size; + const char *cpu_model = args->cpu_model; + const char *kernel_filename = args->kernel_filename; + const char *kernel_cmdline = args->kernel_cmdline; + const char *initrd_filename = args->initrd_filename; + 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 *irqp; + qemu_irq pic[64]; + NICInfo *nd; + int n; + qemu_irq cpu_irq; + + if (!cpu_model) { + cpu_model = "cortex-a9"; + } + + cpu = cpu_arm_init(cpu_model); + if (!cpu) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + irqp = arm_pic_init_cpu(cpu); + cpu_irq = irqp[ARM_PIC_CPU_IRQ]; + + /* max 2GB ram */ + if (ram_size > 0x80000000) { + ram_size = 0x80000000; + } + + /* DDR remapped to address zero. */ + memory_region_init_ram(ext_ram, "zynq.ext_ram", ram_size); + vmstate_register_ram_global(ext_ram); + memory_region_add_subregion(address_space_mem, 0, ext_ram); + + /* 256K of on-chip memory */ + memory_region_init_ram(ocm_ram, "zynq.ocm_ram", 256 << 10); + 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 ? dinfo->bdrv : 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, 0xF8F00000); + sysbus_connect_irq(busdev, 0, 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); + + for (n = 0; n < nb_nics; n++) { + nd = &nd_table[n]; + if (n == 0) { + gem_init(nd, 0xE000B000, pic[54-IRQ_OFFSET]); + } else if (n == 1) { + gem_init(nd, 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]); + + 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_env_get_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, + DEFAULT_MACHINE_OPTIONS, +}; + +static void zynq_machine_init(void) +{ + qemu_register_machine(&zynq_machine); +} + +machine_init(zynq_machine_init); diff --git a/hw/arm/z2.c b/hw/arm/z2.c new file mode 100644 index 0000000000..cbb6d8085e --- /dev/null +++ b/hw/arm/z2.c @@ -0,0 +1,384 @@ +/* + * 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/pxa.h" +#include "hw/arm-misc.h" +#include "hw/devices.h" +#include "hw/i2c.h" +#include "hw/ssi.h" +#include "hw/boards.h" +#include "sysemu/sysemu.h" +#include "hw/flash.h" +#include "sysemu/blockdev.h" +#include "ui/console.h" +#include "audio/audio.h" +#include "exec/address-spaces.h" + +#ifdef DEBUG_Z2 +#define DPRINTF(fmt, ...) \ + printf(fmt, ## __VA_ARGS__) +#else +#define DPRINTF(fmt, ...) +#endif + +static 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, + .minimum_version_id_old = 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, +}; + +typedef struct { + I2CSlave i2c; + int len; + uint8_t buf[3]; +} AER915State; + +static int aer915_send(I2CSlave *i2c, uint8_t data) +{ + AER915State *s = FROM_I2C_SLAVE(AER915State, 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 = FROM_I2C_SLAVE(AER915State, 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) +{ + int retval = 0x00; + AER915State *s = FROM_I2C_SLAVE(AER915State, slave); + + 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, + .minimum_version_id_old = 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 = "aer915", + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(AER915State), + .class_init = aer915_class_init, +}; + +static void z2_init(QEMUMachineInitArgs *args) +{ + const char *cpu_model = args->cpu_model; + const char *kernel_filename = args->kernel_filename; + const char *kernel_cmdline = args->kernel_cmdline; + const char *initrd_filename = args->initrd_filename; + MemoryRegion *address_space_mem = get_system_memory(); + uint32_t sector_len = 0x10000; + PXA2xxState *mpu; + DriveInfo *dinfo; + int be; + void *z2_lcd; + i2c_bus *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) { + 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->bdrv, 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, "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_irqs(z2_lcd_cs, z2_lcd, 1)[0]); + + if (kernel_filename) { + 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, + DEFAULT_MACHINE_OPTIONS, +}; + +static void z2_machine_init(void) +{ + qemu_register_machine(&z2_machine); +} + +machine_init(z2_machine_init); |