diff --git a/Makefile b/Makefile
index c36a978..cb0cf7b 100644
--- a/Makefile
+++ b/Makefile
@@ -51,7 +51,8 @@ OBJS+=block.o
 
 OBJS+=irq.o
 OBJS+=i2c.o smbus.o smbus_eeprom.o max7310.o max111x.o wm8750.o
-OBJS+=ssd0303.o ssd0323.o ads7846.o stellaris_input.o
+OBJS+=ssd0303.o ssd0323.o ads7846.o stellaris_input.o twl92230.o
+OBJS+=tmp105.o
 OBJS+=scsi-disk.o cdrom.o
 OBJS+=scsi-generic.o
 OBJS+=usb.o usb-hub.o usb-linux.o usb-hid.o usb-msd.o usb-wacom.o usb-serial.o
diff --git a/Makefile.target b/Makefile.target
index d1deda1..48f31bc 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -593,7 +593,9 @@ OBJS+= pxa2xx_lcd.o pxa2xx_mmci.o pxa2xx_pcmcia.o pxa2xx_keypad.o
 OBJS+= pflash_cfi01.o gumstix.o
 OBJS+= spitz.o ide.o serial.o nand.o ecc.o
 OBJS+= omap1.o omap_lcdc.o omap_dma.o omap_clk.o omap_mmc.o omap_i2c.o
+OBJS+= omap2.o omap_dss.o
 OBJS+= palm.o tsc210x.o
+OBJS+= nseries.o blizzard.o onenand.o vga.o cbus.o
 OBJS+= mst_fpga.o mainstone.o
 CPPFLAGS += -DHAS_AUDIO
 endif
diff --git a/console.h b/console.h
index b8a5c6d..b45974e 100644
--- a/console.h
+++ b/console.h
@@ -32,6 +32,12 @@ void kbd_put_keycode(int keycode);
 void kbd_mouse_event(int dx, int dy, int dz, int buttons_state);
 int kbd_mouse_is_absolute(void);
 
+struct mouse_transform_info_s {
+    int x;
+    int y;
+    int a[7];
+};
+
 void do_info_mice(void);
 void do_mouse_set(int index);
 
diff --git a/cpu-all.h b/cpu-all.h
index 7a7e655..c7c9611 100644
--- a/cpu-all.h
+++ b/cpu-all.h
@@ -810,7 +810,7 @@ extern uint8_t *phys_ram_dirty;
 /* physical memory access */
 #define TLB_INVALID_MASK   (1 << 3)
 #define IO_MEM_SHIFT       4
-#define IO_MEM_NB_ENTRIES  (1 << (TARGET_PAGE_BITS  - IO_MEM_SHIFT))
+#define IO_MEM_NB_ENTRIES  (16 << (TARGET_PAGE_BITS  - IO_MEM_SHIFT))
 
 #define IO_MEM_RAM         (0 << IO_MEM_SHIFT) /* hardcoded offset */
 #define IO_MEM_ROM         (1 << IO_MEM_SHIFT) /* hardcoded offset */
diff --git a/exec.c b/exec.c
index e9a5918..c69f742 100644
--- a/exec.c
+++ b/exec.c
@@ -1658,7 +1658,7 @@ int tlb_set_page_exec(CPUState *env, target_ulong vaddr,
     {
         if ((pd & ~TARGET_PAGE_MASK) > IO_MEM_ROM && !(pd & IO_MEM_ROMD)) {
             /* IO memory case */
-            address = vaddr | pd;
+            address = vaddr | (pd & ~TARGET_PAGE_MASK);
             addend = paddr;
         } else {
             /* standard memory */
@@ -1692,7 +1692,9 @@ int tlb_set_page_exec(CPUState *env, target_ulong vaddr,
         } else {
             te->addr_read = -1;
         }
-        if (prot & PAGE_EXEC) {
+        if ((pd & ~TARGET_PAGE_MASK) > IO_MEM_ROM && !(pd & IO_MEM_ROMD)) {
+            te->addr_code = pd;
+        } else if (prot & PAGE_EXEC) {
             te->addr_code = address;
         } else {
             te->addr_code = -1;
@@ -2487,7 +2489,9 @@ int cpu_register_io_memory(int io_index,
     if (io_index <= 0) {
         if (io_mem_nb >= IO_MEM_NB_ENTRIES)
             return -1;
-        io_index = io_mem_nb++;
+        do io_index = io_mem_nb++;
+        while (((io_index << IO_MEM_SHIFT) & ~TARGET_PAGE_MASK)
+               <= IO_MEM_NOTDIRTY);
     } else {
         if (io_index >= IO_MEM_NB_ENTRIES)
             return -1;
diff --git a/hw/arm-misc.h b/hw/arm-misc.h
index 7914ff1..a1e0061 100644
--- a/hw/arm-misc.h
+++ b/hw/arm-misc.h
@@ -21,10 +21,7 @@ qemu_irq *armv7m_init(int flash_size, int sram_size,
                       const char *kernel_filename, const char *cpu_model);
 
 /* arm_boot.c */
-
-void arm_load_kernel(CPUState *env, int ram_size, const char *kernel_filename,
-                     const char *kernel_cmdline, const char *initrd_filename,
-                     int board_id, target_phys_addr_t loader_start);
+void arm_load_kernel(CPUState *env, struct arm_boot_info *info);
 
 /* armv7m_nvic.c */
 int system_clock_scale;
diff --git a/hw/arm_boot.c b/hw/arm_boot.c
index 8335e69..20b1512 100644
--- a/hw/arm_boot.c
+++ b/hw/arm_boot.c
@@ -47,21 +47,18 @@ static void main_cpu_reset(void *opaque)
     CPUState *env = opaque;
 
     cpu_reset(env);
-    if (env->kernel_filename)
-        arm_load_kernel(env, env->ram_size, env->kernel_filename,
-                        env->kernel_cmdline, env->initrd_filename,
-                        env->board_id, env->loader_start);
+    if (env->boot_info)
+        arm_load_kernel(env, env->boot_info);
 
     /* TODO:  Reset secondary CPUs.  */
 }
 
-static void set_kernel_args(uint32_t ram_size, int initrd_size,
-                            const char *kernel_cmdline,
-                            target_phys_addr_t loader_start)
+static void set_kernel_args(struct arm_boot_info *info,
+                int initrd_size, void *base)
 {
     uint32_t *p;
 
-    p = (uint32_t *)(phys_ram_base + KERNEL_ARGS_ADDR);
+    p = (uint32_t *)(base + KERNEL_ARGS_ADDR);
     /* ATAG_CORE */
     stl_raw(p++, 5);
     stl_raw(p++, 0x54410001);
@@ -69,46 +66,55 @@ static void set_kernel_args(uint32_t ram_size, int initrd_size,
     stl_raw(p++, 0x1000);
     stl_raw(p++, 0);
     /* ATAG_MEM */
+    /* TODO: multiple chips */
     stl_raw(p++, 4);
     stl_raw(p++, 0x54410002);
-    stl_raw(p++, ram_size);
-    stl_raw(p++, loader_start);
+    stl_raw(p++, info->ram_size);
+    stl_raw(p++, info->loader_start);
     if (initrd_size) {
         /* ATAG_INITRD2 */
         stl_raw(p++, 4);
         stl_raw(p++, 0x54420005);
-        stl_raw(p++, loader_start + INITRD_LOAD_ADDR);
+        stl_raw(p++, info->loader_start + INITRD_LOAD_ADDR);
         stl_raw(p++, initrd_size);
     }
-    if (kernel_cmdline && *kernel_cmdline) {
+    if (info->kernel_cmdline && *info->kernel_cmdline) {
         /* ATAG_CMDLINE */
         int cmdline_size;
 
-        cmdline_size = strlen(kernel_cmdline);
-        memcpy (p + 2, kernel_cmdline, cmdline_size + 1);
+        cmdline_size = strlen(info->kernel_cmdline);
+        memcpy(p + 2, info->kernel_cmdline, cmdline_size + 1);
         cmdline_size = (cmdline_size >> 2) + 1;
         stl_raw(p++, cmdline_size + 2);
         stl_raw(p++, 0x54410009);
         p += cmdline_size;
     }
+    if (info->atag_board) {
+        /* ATAG_BOARD */
+        int atag_board_len;
+
+        atag_board_len = (info->atag_board(info, p + 2) + 3) >> 2;
+        stl_raw(p++, 2 + atag_board_len);
+        stl_raw(p++, 0x414f4d50);
+        p += atag_board_len;
+    }
     /* ATAG_END */
     stl_raw(p++, 0);
     stl_raw(p++, 0);
 }
 
-static void set_kernel_args_old(uint32_t ram_size, int initrd_size,
-                                const char *kernel_cmdline,
-                                target_phys_addr_t loader_start)
+static void set_kernel_args_old(struct arm_boot_info *info,
+                int initrd_size, void *base)
 {
     uint32_t *p;
     unsigned char *s;
 
     /* see linux/include/asm-arm/setup.h */
-    p = (uint32_t *)(phys_ram_base + KERNEL_ARGS_ADDR);
+    p = (uint32_t *)(base + KERNEL_ARGS_ADDR);
     /* page_size */
     stl_raw(p++, 4096);
     /* nr_pages */
-    stl_raw(p++, ram_size / 4096);
+    stl_raw(p++, info->ram_size / 4096);
     /* ramdisk_size */
     stl_raw(p++, 0);
 #define FLAG_READONLY	1
@@ -142,7 +148,7 @@ static void set_kernel_args_old(uint32_t ram_size, int initrd_size,
     stl_raw(p++, 0);
     /* initrd_start */
     if (initrd_size)
-        stl_raw(p++, loader_start + INITRD_LOAD_ADDR);
+        stl_raw(p++, info->loader_start + INITRD_LOAD_ADDR);
     else
         stl_raw(p++, 0);
     /* initrd_size */
@@ -159,17 +165,15 @@ static void set_kernel_args_old(uint32_t ram_size, int initrd_size,
     stl_raw(p++, 0);
     /* zero unused fields */
     memset(p, 0, 256 + 1024 -
-           (p - ((uint32_t *)(phys_ram_base + KERNEL_ARGS_ADDR))));
-    s = phys_ram_base + KERNEL_ARGS_ADDR + 256 + 1024;
-    if (kernel_cmdline)
-        strcpy (s, kernel_cmdline);
+           (p - ((uint32_t *)(base + KERNEL_ARGS_ADDR))));
+    s = base + KERNEL_ARGS_ADDR + 256 + 1024;
+    if (info->kernel_cmdline)
+        strcpy (s, info->kernel_cmdline);
     else
         stb_raw(s, 0);
 }
 
-void arm_load_kernel(CPUState *env, int ram_size, const char *kernel_filename,
-                     const char *kernel_cmdline, const char *initrd_filename,
-                     int board_id, target_phys_addr_t loader_start)
+void arm_load_kernel(CPUState *env, struct arm_boot_info *info)
 {
     int kernel_size;
     int initrd_size;
@@ -177,36 +181,41 @@ void arm_load_kernel(CPUState *env, int ram_size, const char *kernel_filename,
     int is_linux = 0;
     uint64_t elf_entry;
     target_ulong entry;
+    uint32_t pd;
+    void *loader_phys;
 
     /* Load the kernel.  */
-    if (!kernel_filename) {
+    if (!info->kernel_filename) {
         fprintf(stderr, "Kernel image must be specified\n");
         exit(1);
     }
 
-    if (!env->kernel_filename) {
-        env->ram_size = ram_size;
-        env->kernel_filename = kernel_filename;
-        env->kernel_cmdline = kernel_cmdline;
-        env->initrd_filename = initrd_filename;
-        env->board_id = board_id;
-        env->loader_start = loader_start;
+    if (!env->boot_info) {
+        if (info->nb_cpus == 0)
+            info->nb_cpus = 1;
+        env->boot_info = info;
         qemu_register_reset(main_cpu_reset, env);
     }
+
+    pd = cpu_get_physical_page_desc(info->loader_start);
+    loader_phys = phys_ram_base + (pd & TARGET_PAGE_MASK) +
+            (info->loader_start & ~TARGET_PAGE_MASK);
+
     /* Assume that raw images are linux kernels, and ELF images are not.  */
-    kernel_size = load_elf(kernel_filename, 0, &elf_entry, NULL, NULL);
+    kernel_size = load_elf(info->kernel_filename, 0, &elf_entry, NULL, NULL);
     entry = elf_entry;
     if (kernel_size < 0) {
-        kernel_size = load_uboot(kernel_filename, &entry, &is_linux);
+        kernel_size = load_uboot(info->kernel_filename, &entry, &is_linux);
     }
     if (kernel_size < 0) {
-        kernel_size = load_image(kernel_filename,
-                                 phys_ram_base + KERNEL_LOAD_ADDR);
-        entry = loader_start + KERNEL_LOAD_ADDR;
+        kernel_size = load_image(info->kernel_filename,
+                                 loader_phys + KERNEL_LOAD_ADDR);
+        entry = info->loader_start + KERNEL_LOAD_ADDR;
         is_linux = 1;
     }
     if (kernel_size < 0) {
-        fprintf(stderr, "qemu: could not load kernel '%s'\n", kernel_filename);
+        fprintf(stderr, "qemu: could not load kernel '%s'\n",
+                info->kernel_filename);
         exit(1);
     }
     if (!is_linux) {
@@ -214,30 +223,29 @@ void arm_load_kernel(CPUState *env, int ram_size, const char *kernel_filename,
         env->regs[15] = entry & 0xfffffffe;
         env->thumb = entry & 1;
     } else {
-        if (initrd_filename) {
-            initrd_size = load_image(initrd_filename,
-                                     phys_ram_base + INITRD_LOAD_ADDR);
+        if (info->initrd_filename) {
+            initrd_size = load_image(info->initrd_filename,
+                                     loader_phys + INITRD_LOAD_ADDR);
             if (initrd_size < 0) {
                 fprintf(stderr, "qemu: could not load initrd '%s'\n",
-                        initrd_filename);
+                        info->initrd_filename);
                 exit(1);
             }
         } else {
             initrd_size = 0;
         }
-        bootloader[1] |= board_id & 0xff;
-        bootloader[2] |= (board_id >> 8) & 0xff;
-        bootloader[5] = loader_start + KERNEL_ARGS_ADDR;
+        bootloader[1] |= info->board_id & 0xff;
+        bootloader[2] |= (info->board_id >> 8) & 0xff;
+        bootloader[5] = info->loader_start + KERNEL_ARGS_ADDR;
         bootloader[6] = entry;
         for (n = 0; n < sizeof(bootloader) / 4; n++)
-            stl_raw(phys_ram_base + (n * 4), bootloader[n]);
-        for (n = 0; n < sizeof(smpboot) / 4; n++)
-            stl_raw(phys_ram_base + ram_size + (n * 4), smpboot[n]);
+            stl_raw(loader_phys + (n * 4), bootloader[n]);
+        if (info->nb_cpus > 1)
+            for (n = 0; n < sizeof(smpboot) / 4; n++)
+                stl_raw(loader_phys + info->ram_size + (n * 4), smpboot[n]);
         if (old_param)
-            set_kernel_args_old(ram_size, initrd_size,
-                                kernel_cmdline, loader_start);
+            set_kernel_args_old(info, initrd_size, loader_phys);
         else
-            set_kernel_args(ram_size, initrd_size,
-                            kernel_cmdline, loader_start);
+            set_kernel_args(info, initrd_size, loader_phys);
     }
 }
diff --git a/hw/blizzard.c b/hw/blizzard.c
new file mode 100644
index 0000000..9046b5d
--- /dev/null
+++ b/hw/blizzard.c
@@ -0,0 +1,1001 @@
+/*
+ * Epson S1D13744/S1D13745 (Blizzard/Hailstorm/Tornado) LCD/TV controller.
+ *
+ * Copyright (C) 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 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include "qemu-common.h"
+#include "sysemu.h"
+#include "console.h"
+#include "devices.h"
+#include "vga_int.h"
+#include "pixel_ops.h"
+
+typedef void (*blizzard_fn_t)(uint8_t *, const uint8_t *, unsigned int);
+
+struct blizzard_s {
+    uint8_t reg;
+    uint32_t addr;
+    int swallow;
+
+    int pll;
+    int pll_range;
+    int pll_ctrl;
+    uint8_t pll_mode;
+    uint8_t clksel;
+    int memenable;
+    int memrefresh;
+    uint8_t timing[3];
+    int priority;
+
+    uint8_t lcd_config;
+    int x;
+    int y;
+    int skipx;
+    int skipy;
+    uint8_t hndp;
+    uint8_t vndp;
+    uint8_t hsync;
+    uint8_t vsync;
+    uint8_t pclk;
+    uint8_t u;
+    uint8_t v;
+    uint8_t yrc[2];
+    int ix[2];
+    int iy[2];
+    int ox[2];
+    int oy[2];
+
+    int enable;
+    int blank;
+    int bpp;
+    int invalidate;
+    int mx[2];
+    int my[2];
+    uint8_t mode;
+    uint8_t effect;
+    uint8_t iformat;
+    uint8_t source;
+    DisplayState *state;
+    blizzard_fn_t *line_fn_tab[2];
+    void *fb;
+
+    uint8_t hssi_config[3];
+    uint8_t tv_config;
+    uint8_t tv_timing[4];
+    uint8_t vbi;
+    uint8_t tv_x;
+    uint8_t tv_y;
+    uint8_t tv_test;
+    uint8_t tv_filter_config;
+    uint8_t tv_filter_idx;
+    uint8_t tv_filter_coeff[0x20];
+    uint8_t border_r;
+    uint8_t border_g;
+    uint8_t border_b;
+    uint8_t gamma_config;
+    uint8_t gamma_idx;
+    uint8_t gamma_lut[0x100];
+    uint8_t matrix_ena;
+    uint8_t matrix_coeff[0x12];
+    uint8_t matrix_r;
+    uint8_t matrix_g;
+    uint8_t matrix_b;
+    uint8_t pm;
+    uint8_t status;
+    uint8_t rgbgpio_dir;
+    uint8_t rgbgpio;
+    uint8_t gpio_dir;
+    uint8_t gpio;
+    uint8_t gpio_edge[2];
+    uint8_t gpio_irq;
+    uint8_t gpio_pdown;
+
+    struct {
+        int x;
+        int y;
+        int dx;
+        int dy;
+        int len;
+        int buflen;
+        void *buf;
+        void *data;
+        uint16_t *ptr;
+        int angle;
+        int pitch;
+        blizzard_fn_t line_fn;
+    } data;
+};
+
+/* Bytes(!) per pixel */
+static const int blizzard_iformat_bpp[0x10] = {
+    0,
+    2,	/* RGB 5:6:5*/
+    3,	/* RGB 6:6:6 mode 1 */
+    3,	/* RGB 8:8:8 mode 1 */
+    0, 0,
+    4,	/* RGB 6:6:6 mode 2 */
+    4,	/* RGB 8:8:8 mode 2 */
+    0,	/* YUV 4:2:2 */
+    0,	/* YUV 4:2:0 */
+    0, 0, 0, 0, 0, 0,
+};
+
+static inline void blizzard_rgb2yuv(int r, int g, int b,
+                int *y, int *u, int *v)
+{
+    *y = 0x10 + ((0x838 * r + 0x1022 * g + 0x322 * b) >> 13);
+    *u = 0x80 + ((0xe0e * b - 0x04c1 * r - 0x94e * g) >> 13);
+    *v = 0x80 + ((0xe0e * r - 0x0bc7 * g - 0x247 * b) >> 13);
+}
+
+static void blizzard_window(struct blizzard_s *s)
+{
+    uint8_t *src, *dst;
+    int bypp[2];
+    int bypl[3];
+    int y;
+    blizzard_fn_t fn = s->data.line_fn;
+
+    if (!fn)
+        return;
+    if (s->mx[0] > s->data.x)
+        s->mx[0] = s->data.x;
+    if (s->my[0] > s->data.y)
+        s->my[0] = s->data.y;
+    if (s->mx[1] < s->data.x + s->data.dx)
+        s->mx[1] = s->data.x + s->data.dx;
+    if (s->my[1] < s->data.y + s->data.dy)
+        s->my[1] = s->data.y + s->data.dy;
+
+    bypp[0] = s->bpp;
+    bypp[1] = (s->state->depth + 7) >> 3;
+    bypl[0] = bypp[0] * s->data.pitch;
+    bypl[1] = bypp[1] * s->x;
+    bypl[2] = bypp[0] * s->data.dx;
+
+    src = s->data.data;
+    dst = s->fb + bypl[1] * s->data.y + bypp[1] * s->data.x;
+    for (y = s->data.dy; y > 0; y --, src += bypl[0], dst += bypl[1])
+        fn(dst, src, bypl[2]);
+}
+
+static int blizzard_transfer_setup(struct blizzard_s *s)
+{
+    if (s->source > 3 || !s->bpp ||
+                    s->ix[1] < s->ix[0] || s->iy[1] < s->iy[0])
+        return 0;
+
+    s->data.angle = s->effect & 3;
+    s->data.line_fn = s->line_fn_tab[!!s->data.angle][s->iformat];
+    s->data.x = s->ix[0];
+    s->data.y = s->iy[0];
+    s->data.dx = s->ix[1] - s->ix[0] + 1;
+    s->data.dy = s->iy[1] - s->iy[0] + 1;
+    s->data.len = s->bpp * s->data.dx * s->data.dy;
+    s->data.pitch = s->data.dx;
+    if (s->data.len > s->data.buflen) {
+        s->data.buf = realloc(s->data.buf, s->data.len);
+        s->data.buflen = s->data.len;
+    }
+    s->data.ptr = s->data.buf;
+    s->data.data = s->data.buf;
+    s->data.len /= 2;
+    return 1;
+}
+
+static void blizzard_reset(struct blizzard_s *s)
+{
+    s->reg = 0;
+    s->swallow = 0;
+
+    s->pll = 9;
+    s->pll_range = 1;
+    s->pll_ctrl = 0x14;
+    s->pll_mode = 0x32;
+    s->clksel = 0x00;
+    s->memenable = 0;
+    s->memrefresh = 0x25c;
+    s->timing[0] = 0x3f;
+    s->timing[1] = 0x13;
+    s->timing[2] = 0x21;
+    s->priority = 0;
+
+    s->lcd_config = 0x74;
+    s->x = 8;
+    s->y = 1;
+    s->skipx = 0;
+    s->skipy = 0;
+    s->hndp = 3;
+    s->vndp = 2;
+    s->hsync = 1;
+    s->vsync = 1;
+    s->pclk = 0x80;
+
+    s->ix[0] = 0;
+    s->ix[1] = 0;
+    s->iy[0] = 0;
+    s->iy[1] = 0;
+    s->ox[0] = 0;
+    s->ox[1] = 0;
+    s->oy[0] = 0;
+    s->oy[1] = 0;
+
+    s->yrc[0] = 0x00;
+    s->yrc[1] = 0x30;
+    s->u = 0;
+    s->v = 0;
+
+    s->iformat = 3;
+    s->source = 0;
+    s->bpp = blizzard_iformat_bpp[s->iformat];
+
+    s->hssi_config[0] = 0x00;
+    s->hssi_config[1] = 0x00;
+    s->hssi_config[2] = 0x01;
+    s->tv_config = 0x00;
+    s->tv_timing[0] = 0x00;
+    s->tv_timing[1] = 0x00;
+    s->tv_timing[2] = 0x00;
+    s->tv_timing[3] = 0x00;
+    s->vbi = 0x10;
+    s->tv_x = 0x14;
+    s->tv_y = 0x03;
+    s->tv_test = 0x00;
+    s->tv_filter_config = 0x80;
+    s->tv_filter_idx = 0x00;
+    s->border_r = 0x10;
+    s->border_g = 0x80;
+    s->border_b = 0x80;
+    s->gamma_config = 0x00;
+    s->gamma_idx = 0x00;
+    s->matrix_ena = 0x00;
+    memset(&s->matrix_coeff, 0, sizeof(s->matrix_coeff));
+    s->matrix_r = 0x00;
+    s->matrix_g = 0x00;
+    s->matrix_b = 0x00;
+    s->pm = 0x02;
+    s->status = 0x00;
+    s->rgbgpio_dir = 0x00;
+    s->gpio_dir = 0x00;
+    s->gpio_edge[0] = 0x00;
+    s->gpio_edge[1] = 0x00;
+    s->gpio_irq = 0x00;
+    s->gpio_pdown = 0xff;
+}
+
+static inline void blizzard_invalidate_display(void *opaque) {
+    struct blizzard_s *s = (struct blizzard_s *) opaque;
+
+    s->invalidate = 1;
+}
+
+static uint16_t blizzard_reg_read(void *opaque, uint8_t reg)
+{
+    struct blizzard_s *s = (struct blizzard_s *) opaque;
+
+    switch (reg) {
+    case 0x00:	/* Revision Code */
+        return 0xa5;
+
+    case 0x02:	/* Configuration Readback */
+        return 0x83;	/* Macrovision OK, CNF[2:0] = 3 */
+
+    case 0x04:	/* PLL M-Divider */
+        return (s->pll - 1) | (1 << 7);
+    case 0x06:	/* PLL Lock Range Control */
+        return s->pll_range;
+    case 0x08:	/* PLL Lock Synthesis Control 0 */
+        return s->pll_ctrl & 0xff;
+    case 0x0a:	/* PLL Lock Synthesis Control 1 */
+        return s->pll_ctrl >> 8;
+    case 0x0c:	/* PLL Mode Control 0 */
+        return s->pll_mode;
+
+    case 0x0e:	/* Clock-Source Select */
+        return s->clksel;
+
+    case 0x10:	/* Memory Controller Activate */
+    case 0x14:	/* Memory Controller Bank 0 Status Flag */
+        return s->memenable;
+
+    case 0x18:	/* Auto-Refresh Interval Setting 0 */
+        return s->memrefresh & 0xff;
+    case 0x1a:	/* Auto-Refresh Interval Setting 1 */
+        return s->memrefresh >> 8;
+
+    case 0x1c:	/* Power-On Sequence Timing Control */
+        return s->timing[0];
+    case 0x1e:	/* Timing Control 0 */
+        return s->timing[1];
+    case 0x20:	/* Timing Control 1 */
+        return s->timing[2];
+
+    case 0x24:	/* Arbitration Priority Control */
+        return s->priority;
+
+    case 0x28:	/* LCD Panel Configuration */
+        return s->lcd_config;
+
+    case 0x2a:	/* LCD Horizontal Display Width */
+        return s->x >> 3;
+    case 0x2c:	/* LCD Horizontal Non-display Period */
+        return s->hndp;
+    case 0x2e:	/* LCD Vertical Display Height 0 */
+        return s->y & 0xff;
+    case 0x30:	/* LCD Vertical Display Height 1 */
+        return s->y >> 8;
+    case 0x32:	/* LCD Vertical Non-display Period */
+        return s->vndp;
+    case 0x34:	/* LCD HS Pulse-width */
+        return s->hsync;
+    case 0x36:	/* LCd HS Pulse Start Position */
+        return s->skipx >> 3;
+    case 0x38:	/* LCD VS Pulse-width */
+        return s->vsync;
+    case 0x3a:	/* LCD VS Pulse Start Position */
+        return s->skipy;
+
+    case 0x3c:	/* PCLK Polarity */
+        return s->pclk;
+
+    case 0x3e:	/* High-speed Serial Interface Tx Configuration Port 0 */
+        return s->hssi_config[0];
+    case 0x40:	/* High-speed Serial Interface Tx Configuration Port 1 */
+        return s->hssi_config[1];
+    case 0x42:	/* High-speed Serial Interface Tx Mode */
+        return s->hssi_config[2];
+    case 0x44:	/* TV Display Configuration */
+        return s->tv_config;
+    case 0x46 ... 0x4c:	/* TV Vertical Blanking Interval Data bits */
+        return s->tv_timing[(reg - 0x46) >> 1];
+    case 0x4e:	/* VBI: Closed Caption / XDS Control / Status */
+        return s->vbi;
+    case 0x50:	/* TV Horizontal Start Position */
+        return s->tv_x;
+    case 0x52:	/* TV Vertical Start Position */
+        return s->tv_y;
+    case 0x54:	/* TV Test Pattern Setting */
+        return s->tv_test;
+    case 0x56:	/* TV Filter Setting */
+        return s->tv_filter_config;
+    case 0x58:	/* TV Filter Coefficient Index */
+        return s->tv_filter_idx;
+    case 0x5a:	/* TV Filter Coefficient Data */
+        if (s->tv_filter_idx < 0x20)
+            return s->tv_filter_coeff[s->tv_filter_idx ++];
+        return 0;
+
+    case 0x60:	/* Input YUV/RGB Translate Mode 0 */
+        return s->yrc[0];
+    case 0x62:	/* Input YUV/RGB Translate Mode 1 */
+        return s->yrc[1];
+    case 0x64:	/* U Data Fix */
+        return s->u;
+    case 0x66:	/* V Data Fix */
+        return s->v;
+
+    case 0x68:	/* Display Mode */
+        return s->mode;
+
+    case 0x6a:	/* Special Effects */
+        return s->effect;
+
+    case 0x6c:	/* Input Window X Start Position 0 */
+        return s->ix[0] & 0xff;
+    case 0x6e:	/* Input Window X Start Position 1 */
+        return s->ix[0] >> 3;
+    case 0x70:	/* Input Window Y Start Position 0 */
+        return s->ix[0] & 0xff;
+    case 0x72:	/* Input Window Y Start Position 1 */
+        return s->ix[0] >> 3;
+    case 0x74:	/* Input Window X End Position 0 */
+        return s->ix[1] & 0xff;
+    case 0x76:	/* Input Window X End Position 1 */
+        return s->ix[1] >> 3;
+    case 0x78:	/* Input Window Y End Position 0 */
+        return s->ix[1] & 0xff;
+    case 0x7a:	/* Input Window Y End Position 1 */
+        return s->ix[1] >> 3;
+    case 0x7c:	/* Output Window X Start Position 0 */
+        return s->ox[0] & 0xff;
+    case 0x7e:	/* Output Window X Start Position 1 */
+        return s->ox[0] >> 3;
+    case 0x80:	/* Output Window Y Start Position 0 */
+        return s->oy[0] & 0xff;
+    case 0x82:	/* Output Window Y Start Position 1 */
+        return s->oy[0] >> 3;
+    case 0x84:	/* Output Window X End Position 0 */
+        return s->ox[1] & 0xff;
+    case 0x86:	/* Output Window X End Position 1 */
+        return s->ox[1] >> 3;
+    case 0x88:	/* Output Window Y End Position 0 */
+        return s->oy[1] & 0xff;
+    case 0x8a:	/* Output Window Y End Position 1 */
+        return s->oy[1] >> 3;
+
+    case 0x8c:	/* Input Data Format */
+        return s->iformat;
+    case 0x8e:	/* Data Source Select */
+        return s->source;
+    case 0x90:	/* Display Memory Data Port */
+        return 0;
+
+    case 0xa8:	/* Border Color 0 */
+        return s->border_r;
+    case 0xaa:	/* Border Color 1 */
+        return s->border_g;
+    case 0xac:	/* Border Color 2 */
+        return s->border_b;
+
+    case 0xb4:	/* Gamma Correction Enable */
+        return s->gamma_config;
+    case 0xb6:	/* Gamma Correction Table Index */
+        return s->gamma_idx;
+    case 0xb8:	/* Gamma Correction Table Data */
+        return s->gamma_lut[s->gamma_idx ++];
+
+    case 0xba:	/* 3x3 Matrix Enable */
+        return s->matrix_ena;
+    case 0xbc ... 0xde:	/* Coefficient Registers */
+        return s->matrix_coeff[(reg - 0xbc) >> 1];
+    case 0xe0:	/* 3x3 Matrix Red Offset */
+        return s->matrix_r;
+    case 0xe2:	/* 3x3 Matrix Green Offset */
+        return s->matrix_g;
+    case 0xe4:	/* 3x3 Matrix Blue Offset */
+        return s->matrix_b;
+
+    case 0xe6:	/* Power-save */
+        return s->pm;
+    case 0xe8:	/* Non-display Period Control / Status */
+        return s->status | (1 << 5);
+    case 0xea:	/* RGB Interface Control */
+        return s->rgbgpio_dir;
+    case 0xec:	/* RGB Interface Status */
+        return s->rgbgpio;
+    case 0xee:	/* General-purpose IO Pins Configuration */
+        return s->gpio_dir;
+    case 0xf0:	/* General-purpose IO Pins Status / Control */
+        return s->gpio;
+    case 0xf2:	/* GPIO Positive Edge Interrupt Trigger */
+        return s->gpio_edge[0];
+    case 0xf4:	/* GPIO Negative Edge Interrupt Trigger */
+        return s->gpio_edge[1];
+    case 0xf6:	/* GPIO Interrupt Status */
+        return s->gpio_irq;
+    case 0xf8:	/* GPIO Pull-down Control */
+        return s->gpio_pdown;
+
+    default:
+        fprintf(stderr, "%s: unknown register %02x\n", __FUNCTION__, reg);
+        return 0;
+    }
+}
+
+static void blizzard_reg_write(void *opaque, uint8_t reg, uint16_t value)
+{
+    struct blizzard_s *s = (struct blizzard_s *) opaque;
+
+    switch (reg) {
+    case 0x04:	/* PLL M-Divider */
+        s->pll = (value & 0x3f) + 1;
+        break;
+    case 0x06:	/* PLL Lock Range Control */
+        s->pll_range = value & 3;
+        break;
+    case 0x08:	/* PLL Lock Synthesis Control 0 */
+        s->pll_ctrl &= 0xf00;
+        s->pll_ctrl |= (value << 0) & 0x0ff;
+        break;
+    case 0x0a:	/* PLL Lock Synthesis Control 1 */
+        s->pll_ctrl &= 0x0ff;
+        s->pll_ctrl |= (value << 8) & 0xf00;
+        break;
+    case 0x0c:	/* PLL Mode Control 0 */
+        s->pll_mode = value & 0x77;
+        if ((value & 3) == 0 || (value & 3) == 3)
+            fprintf(stderr, "%s: wrong PLL Control bits (%i)\n",
+                    __FUNCTION__, value & 3);
+        break;
+
+    case 0x0e:	/* Clock-Source Select */
+        s->clksel = value & 0xff;
+        break;
+
+    case 0x10:	/* Memory Controller Activate */
+        s->memenable = value & 1;
+        break;
+    case 0x14:	/* Memory Controller Bank 0 Status Flag */
+        break;
+
+    case 0x18:	/* Auto-Refresh Interval Setting 0 */
+        s->memrefresh &= 0xf00;
+        s->memrefresh |= (value << 0) & 0x0ff;
+        break;
+    case 0x1a:	/* Auto-Refresh Interval Setting 1 */
+        s->memrefresh &= 0x0ff;
+        s->memrefresh |= (value << 8) & 0xf00;
+        break;
+
+    case 0x1c:	/* Power-On Sequence Timing Control */
+        s->timing[0] = value & 0x7f;
+        break;
+    case 0x1e:	/* Timing Control 0 */
+        s->timing[1] = value & 0x17;
+        break;
+    case 0x20:	/* Timing Control 1 */
+        s->timing[2] = value & 0x35;
+        break;
+
+    case 0x24:	/* Arbitration Priority Control */
+        s->priority = value & 1;
+        break;
+
+    case 0x28:	/* LCD Panel Configuration */
+        s->lcd_config = value & 0xff;
+        if (value & (1 << 7))
+            fprintf(stderr, "%s: data swap not supported!\n", __FUNCTION__);
+        break;
+
+    case 0x2a:	/* LCD Horizontal Display Width */
+        s->x = value << 3;
+        break;
+    case 0x2c:	/* LCD Horizontal Non-display Period */
+        s->hndp = value & 0xff;
+        break;
+    case 0x2e:	/* LCD Vertical Display Height 0 */
+        s->y &= 0x300;
+        s->y |= (value << 0) & 0x0ff;
+        break;
+    case 0x30:	/* LCD Vertical Display Height 1 */
+        s->y &= 0x0ff;
+        s->y |= (value << 8) & 0x300;
+        break;
+    case 0x32:	/* LCD Vertical Non-display Period */
+        s->vndp = value & 0xff;
+        break;
+    case 0x34:	/* LCD HS Pulse-width */
+        s->hsync = value & 0xff;
+        break;
+    case 0x36:	/* LCD HS Pulse Start Position */
+        s->skipx = value & 0xff;
+        break;
+    case 0x38:	/* LCD VS Pulse-width */
+        s->vsync = value & 0xbf;
+        break;
+    case 0x3a:	/* LCD VS Pulse Start Position */
+        s->skipy = value & 0xff;
+        break;
+
+    case 0x3c:	/* PCLK Polarity */
+        s->pclk = value & 0x82;
+        /* Affects calculation of s->hndp, s->hsync and s->skipx.  */
+        break;
+
+    case 0x3e:	/* High-speed Serial Interface Tx Configuration Port 0 */
+        s->hssi_config[0] = value;
+        break;
+    case 0x40:	/* High-speed Serial Interface Tx Configuration Port 1 */
+        s->hssi_config[1] = value;
+        if (((value >> 4) & 3) == 3)
+            fprintf(stderr, "%s: Illegal active-data-links value\n",
+                            __FUNCTION__);
+        break;
+    case 0x42:	/* High-speed Serial Interface Tx Mode */
+        s->hssi_config[2] = value & 0xbd;
+        break;
+
+    case 0x44:	/* TV Display Configuration */
+        s->tv_config = value & 0xfe;
+        break;
+    case 0x46 ... 0x4c:	/* TV Vertical Blanking Interval Data bits 0 */
+        s->tv_timing[(reg - 0x46) >> 1] = value;
+        break;
+    case 0x4e:	/* VBI: Closed Caption / XDS Control / Status */
+        s->vbi = value;
+        break;
+    case 0x50:	/* TV Horizontal Start Position */
+        s->tv_x = value;
+        break;
+    case 0x52:	/* TV Vertical Start Position */
+        s->tv_y = value & 0x7f;
+        break;
+    case 0x54:	/* TV Test Pattern Setting */
+        s->tv_test = value;
+        break;
+    case 0x56:	/* TV Filter Setting */
+        s->tv_filter_config = value & 0xbf;
+        break;
+    case 0x58:	/* TV Filter Coefficient Index */
+        s->tv_filter_idx = value & 0x1f;
+        break;
+    case 0x5a:	/* TV Filter Coefficient Data */
+        if (s->tv_filter_idx < 0x20)
+            s->tv_filter_coeff[s->tv_filter_idx ++] = value;
+        break;
+
+    case 0x60:	/* Input YUV/RGB Translate Mode 0 */
+        s->yrc[0] = value & 0xb0;
+        break;
+    case 0x62:	/* Input YUV/RGB Translate Mode 1 */
+        s->yrc[1] = value & 0x30;
+        break;
+    case 0x64:	/* U Data Fix */
+        s->u = value & 0xff;
+        break;
+    case 0x66:	/* V Data Fix */
+        s->v = value & 0xff;
+        break;
+
+    case 0x68:	/* Display Mode */
+        if ((s->mode ^ value) & 3)
+            s->invalidate = 1;
+        s->mode = value & 0xb7;
+        s->enable = value & 1;
+        s->blank = (value >> 1) & 1;
+        if (value & (1 << 4))
+            fprintf(stderr, "%s: Macrovision enable attempt!\n", __FUNCTION__);
+        break;
+
+    case 0x6a:	/* Special Effects */
+        s->effect = value & 0xfb;
+        break;
+
+    case 0x6c:	/* Input Window X Start Position 0 */
+        s->ix[0] &= 0x300;
+        s->ix[0] |= (value << 0) & 0x0ff;
+        break;
+    case 0x6e:	/* Input Window X Start Position 1 */
+        s->ix[0] &= 0x0ff;
+        s->ix[0] |= (value << 8) & 0x300;
+        break;
+    case 0x70:	/* Input Window Y Start Position 0 */
+        s->iy[0] &= 0x300;
+        s->iy[0] |= (value << 0) & 0x0ff;
+        break;
+    case 0x72:	/* Input Window Y Start Position 1 */
+        s->iy[0] &= 0x0ff;
+        s->iy[0] |= (value << 8) & 0x300;
+        break;
+    case 0x74:	/* Input Window X End Position 0 */
+        s->ix[1] &= 0x300;
+        s->ix[1] |= (value << 0) & 0x0ff;
+        break;
+    case 0x76:	/* Input Window X End Position 1 */
+        s->ix[1] &= 0x0ff;
+        s->ix[1] |= (value << 8) & 0x300;
+        break;
+    case 0x78:	/* Input Window Y End Position 0 */
+        s->iy[1] &= 0x300;
+        s->iy[1] |= (value << 0) & 0x0ff;
+        break;
+    case 0x7a:	/* Input Window Y End Position 1 */
+        s->iy[1] &= 0x0ff;
+        s->iy[1] |= (value << 8) & 0x300;
+        break;
+    case 0x7c:	/* Output Window X Start Position 0 */
+        s->ox[0] &= 0x300;
+        s->ox[0] |= (value << 0) & 0x0ff;
+        break;
+    case 0x7e:	/* Output Window X Start Position 1 */
+        s->ox[0] &= 0x0ff;
+        s->ox[0] |= (value << 8) & 0x300;
+        break;
+    case 0x80:	/* Output Window Y Start Position 0 */
+        s->oy[0] &= 0x300;
+        s->oy[0] |= (value << 0) & 0x0ff;
+        break;
+    case 0x82:	/* Output Window Y Start Position 1 */
+        s->oy[0] &= 0x0ff;
+        s->oy[0] |= (value << 8) & 0x300;
+        break;
+    case 0x84:	/* Output Window X End Position 0 */
+        s->ox[1] &= 0x300;
+        s->ox[1] |= (value << 0) & 0x0ff;
+        break;
+    case 0x86:	/* Output Window X End Position 1 */
+        s->ox[1] &= 0x0ff;
+        s->ox[1] |= (value << 8) & 0x300;
+        break;
+    case 0x88:	/* Output Window Y End Position 0 */
+        s->oy[1] &= 0x300;
+        s->oy[1] |= (value << 0) & 0x0ff;
+        break;
+    case 0x8a:	/* Output Window Y End Position 1 */
+        s->oy[1] &= 0x0ff;
+        s->oy[1] |= (value << 8) & 0x300;
+        break;
+
+    case 0x8c:	/* Input Data Format */
+        s->iformat = value & 0xf;
+        s->bpp = blizzard_iformat_bpp[s->iformat];
+        if (!s->bpp)
+            fprintf(stderr, "%s: Illegal or unsupported input format %x\n",
+                            __FUNCTION__, s->iformat);
+        break;
+    case 0x8e:	/* Data Source Select */
+        s->source = value & 7;
+        /* Currently all windows will be "destructive overlays".  */
+        if ((!(s->effect & (1 << 3)) && (s->ix[0] != s->ox[0] ||
+                                        s->iy[0] != s->oy[0] ||
+                                        s->ix[1] != s->ox[1] ||
+                                        s->iy[1] != s->oy[1])) ||
+                        !((s->ix[1] - s->ix[0]) & (s->iy[1] - s->iy[0]) &
+                          (s->ox[1] - s->ox[0]) & (s->oy[1] - s->oy[0]) & 1))
+            fprintf(stderr, "%s: Illegal input/output window positions\n",
+                            __FUNCTION__);
+
+        blizzard_transfer_setup(s);
+        break;
+
+    case 0x90:	/* Display Memory Data Port */
+        if (!s->data.len && !blizzard_transfer_setup(s))
+            break;
+
+        *s->data.ptr ++ = value;
+        if (-- s->data.len == 0)
+            blizzard_window(s);
+        break;
+
+    case 0xa8:	/* Border Color 0 */
+        s->border_r = value;
+        break;
+    case 0xaa:	/* Border Color 1 */
+        s->border_g = value;
+        break;
+    case 0xac:	/* Border Color 2 */
+        s->border_b = value;
+        break;
+
+    case 0xb4:	/* Gamma Correction Enable */
+        s->gamma_config = value & 0x87;
+        break;
+    case 0xb6:	/* Gamma Correction Table Index */
+        s->gamma_idx = value;
+        break;
+    case 0xb8:	/* Gamma Correction Table Data */
+        s->gamma_lut[s->gamma_idx ++] = value;
+        break;
+
+    case 0xba:	/* 3x3 Matrix Enable */
+        s->matrix_ena = value & 1;
+        break;
+    case 0xbc ... 0xde:	/* Coefficient Registers */
+        s->matrix_coeff[(reg - 0xbc) >> 1] = value & ((reg & 2) ? 0x80 : 0xff);
+        break;
+    case 0xe0:	/* 3x3 Matrix Red Offset */
+        s->matrix_r = value;
+        break;
+    case 0xe2:	/* 3x3 Matrix Green Offset */
+        s->matrix_g = value;
+        break;
+    case 0xe4:	/* 3x3 Matrix Blue Offset */
+        s->matrix_b = value;
+        break;
+
+    case 0xe6:	/* Power-save */
+        s->pm = value & 0x83;
+        if (value & s->mode & 1)
+            fprintf(stderr, "%s: The display must be disabled before entering "
+                            "Standby Mode\n", __FUNCTION__);
+        break;
+    case 0xe8:	/* Non-display Period Control / Status */
+        s->status = value & 0x1b;
+        break;
+    case 0xea:	/* RGB Interface Control */
+        s->rgbgpio_dir = value & 0x8f;
+        break;
+    case 0xec:	/* RGB Interface Status */
+        s->rgbgpio = value & 0xcf;
+        break;
+    case 0xee:	/* General-purpose IO Pins Configuration */
+        s->gpio_dir = value;
+        break;
+    case 0xf0:	/* General-purpose IO Pins Status / Control */
+        s->gpio = value;
+        break;
+    case 0xf2:	/* GPIO Positive Edge Interrupt Trigger */
+        s->gpio_edge[0] = value;
+        break;
+    case 0xf4:	/* GPIO Negative Edge Interrupt Trigger */
+        s->gpio_edge[1] = value;
+        break;
+    case 0xf6:	/* GPIO Interrupt Status */
+        s->gpio_irq &= value;
+        break;
+    case 0xf8:	/* GPIO Pull-down Control */
+        s->gpio_pdown = value;
+        break;
+
+    default:
+        fprintf(stderr, "%s: unknown register %02x\n", __FUNCTION__, reg);
+        break;
+    }
+}
+
+uint16_t s1d13745_read(void *opaque, int dc)
+{
+    struct blizzard_s *s = (struct blizzard_s *) opaque;
+    uint16_t value = blizzard_reg_read(s, s->reg);
+
+    if (s->swallow -- > 0)
+        return 0;
+    if (dc)
+        s->reg ++;
+
+    return value;
+}
+
+void s1d13745_write(void *opaque, int dc, uint16_t value)
+{
+    struct blizzard_s *s = (struct blizzard_s *) opaque;
+
+    if (s->swallow -- > 0)
+        return;
+    if (dc) {
+        blizzard_reg_write(s, s->reg, value);
+
+        if (s->reg != 0x90 && s->reg != 0x5a && s->reg != 0xb8)
+            s->reg += 2;
+    } else
+        s->reg = value & 0xff;
+}
+
+void s1d13745_write_block(void *opaque, int dc,
+                void *buf, size_t len, int pitch)
+{
+    struct blizzard_s *s = (struct blizzard_s *) opaque;
+
+    while (len > 0) {
+        if (s->reg == 0x90 && dc &&
+                        (s->data.len || blizzard_transfer_setup(s)) &&
+                        len >= (s->data.len << 1)) {
+            len -= s->data.len << 1;
+            s->data.len = 0;
+            s->data.data = buf;
+            if (pitch)
+                s->data.pitch = pitch;
+            blizzard_window(s);
+            s->data.data = s->data.buf;
+            continue;
+        }
+
+        s1d13745_write(opaque, dc, *(uint16_t *) buf);
+        len -= 2;
+        buf += 2;
+    }
+
+    return;
+}
+
+static void blizzard_update_display(void *opaque)
+{
+    struct blizzard_s *s = (struct blizzard_s *) opaque;
+    int y, bypp, bypl, bwidth;
+    uint8_t *src, *dst;
+
+    if (!s->enable)
+        return;
+
+    if (s->x != s->state->width || s->y != s->state->height) {
+        s->invalidate = 1;
+        dpy_resize(s->state, s->x, s->y);
+    }
+
+    if (s->invalidate) {
+        s->invalidate = 0;
+
+        if (s->blank) {
+            bypp = (s->state->depth + 7) >> 3;
+            memset(s->state->data, 0, bypp * s->x * s->y);
+            return;
+        }
+
+        s->mx[0] = 0;
+        s->mx[1] = s->x;
+        s->my[0] = 0;
+        s->my[1] = s->y;
+    }
+
+    if (s->mx[1] <= s->mx[0])
+        return;
+
+    bypp = (s->state->depth + 7) >> 3;
+    bypl = bypp * s->x;
+    bwidth = bypp * (s->mx[1] - s->mx[0]);
+    y = s->my[0];
+    src = s->fb + bypl * y + bypp * s->mx[0];
+    dst = s->state->data + bypl * y + bypp * s->mx[0];
+    for (; y < s->my[1]; y ++, src += bypl, dst += bypl)
+        memcpy(dst, src, bwidth);
+
+    dpy_update(s->state, s->mx[0], s->my[0],
+                    s->mx[1] - s->mx[0], y - s->my[0]);
+
+    s->mx[0] = s->x;
+    s->mx[1] = 0;
+    s->my[0] = s->y;
+    s->my[1] = 0;
+}
+
+static void blizzard_screen_dump(void *opaque, const char *filename) {
+    struct blizzard_s *s = (struct blizzard_s *) opaque;
+
+    blizzard_update_display(opaque);
+    if (s && s->state->data)
+        ppm_save(filename, s->state->data, s->x, s->y, s->state->linesize);
+}
+
+#define DEPTH 8
+#include "blizzard_template.h"
+#define DEPTH 15
+#include "blizzard_template.h"
+#define DEPTH 16
+#include "blizzard_template.h"
+#define DEPTH 24
+#include "blizzard_template.h"
+#define DEPTH 32
+#include "blizzard_template.h"
+
+void *s1d13745_init(qemu_irq gpio_int, DisplayState *ds)
+{
+    struct blizzard_s *s = (struct blizzard_s *) qemu_mallocz(sizeof(*s));
+
+    s->state = ds;
+    s->fb = qemu_malloc(0x180000);
+
+    switch (s->state->depth) {
+    case 0:
+        s->line_fn_tab[0] = s->line_fn_tab[1] =
+                qemu_mallocz(sizeof(blizzard_fn_t) * 0x10);
+        break;
+    case 8:
+        s->line_fn_tab[0] = blizzard_draw_fn_8;
+        s->line_fn_tab[1] = blizzard_draw_fn_r_8;
+        break;
+    case 15:
+        s->line_fn_tab[0] = blizzard_draw_fn_15;
+        s->line_fn_tab[1] = blizzard_draw_fn_r_15;
+        break;
+    case 16:
+        s->line_fn_tab[0] = blizzard_draw_fn_16;
+        s->line_fn_tab[1] = blizzard_draw_fn_r_16;
+        break;
+    case 24:
+        s->line_fn_tab[0] = blizzard_draw_fn_24;
+        s->line_fn_tab[1] = blizzard_draw_fn_r_24;
+        break;
+    case 32:
+        s->line_fn_tab[0] = blizzard_draw_fn_32;
+        s->line_fn_tab[1] = blizzard_draw_fn_r_32;
+        break;
+    default:
+        fprintf(stderr, "%s: Bad color depth\n", __FUNCTION__);
+        exit(1);
+    }
+
+    blizzard_reset(s);
+
+    graphic_console_init(s->state, blizzard_update_display,
+                    blizzard_invalidate_display, blizzard_screen_dump,
+                    NULL, s);
+
+    return s;
+}
diff --git a/hw/blizzard_template.h b/hw/blizzard_template.h
new file mode 100644
index 0000000..8c6451d
--- /dev/null
+++ b/hw/blizzard_template.h
@@ -0,0 +1,138 @@
+/*
+ * QEMU Epson S1D13744/S1D13745 templates
+ *
+ * Copyright (C) 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 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#define SKIP_PIXEL(to)		to += deststep
+#if DEPTH == 8
+# define PIXEL_TYPE		uint8_t
+# define COPY_PIXEL(to, from)	*to = from; SKIP_PIXEL(to)
+# define COPY_PIXEL1(to, from)	*to ++ = from
+#elif DEPTH == 15 || DEPTH == 16
+# define PIXEL_TYPE		uint16_t
+# define COPY_PIXEL(to, from)	*to = from; SKIP_PIXEL(to)
+# define COPY_PIXEL1(to, from)	*to ++ = from
+#elif DEPTH == 24
+# define PIXEL_TYPE		uint8_t
+# define COPY_PIXEL(to, from)	\
+    to[0] = from; to[1] = (from) >> 8; to[2] = (from) >> 16; SKIP_PIXEL(to)
+# define COPY_PIXEL1(to, from)	\
+    *to ++ = from; *to ++ = (from) >> 8; *to ++ = (from) >> 16
+#elif DEPTH == 32
+# define PIXEL_TYPE		uint32_t
+# define COPY_PIXEL(to, from)	*to = from; SKIP_PIXEL(to)
+# define COPY_PIXEL1(to, from)	*to ++ = from
+#else
+# error unknown bit depth
+#endif
+
+#ifdef WORDS_BIGENDIAN
+# define SWAP_WORDS	1
+#endif
+
+static void glue(blizzard_draw_line16_, DEPTH)(PIXEL_TYPE *dest,
+                const uint16_t *src, unsigned int width)
+{
+#if !defined(SWAP_WORDS) && DEPTH == 16
+    memcpy(dest, src, width << 1);
+#else
+    uint16_t data;
+    unsigned int r, g, b;
+    const uint16_t *end = (void *) src + width;
+    while (src < end) {
+        data = lduw_raw(src ++);
+        b = (data & 0x1f) << 3;
+        data >>= 5;
+        g = (data & 0x3f) << 2;
+        data >>= 6;
+        r = (data & 0x1f) << 3;
+        data >>= 5;
+        COPY_PIXEL1(dest, glue(rgb_to_pixel, DEPTH)(r, g, b));
+    }
+#endif
+}
+
+static void glue(blizzard_draw_line24mode1_, DEPTH)(PIXEL_TYPE *dest,
+                const uint8_t *src, unsigned int width)
+{
+    /* TODO: check if SDL 24-bit planes are not in the same format and
+     * if so, use memcpy */
+    unsigned int r[2], g[2], b[2];
+    const uint8_t *end = src + width;
+    while (src < end) {
+        g[0] = *src ++;
+        r[0] = *src ++;
+        r[1] = *src ++;
+        b[0] = *src ++;
+        COPY_PIXEL1(dest, glue(rgb_to_pixel, DEPTH)(r[0], g[0], b[0]));
+        b[1] = *src ++;
+        g[1] = *src ++;
+        COPY_PIXEL1(dest, glue(rgb_to_pixel, DEPTH)(r[1], g[1], b[1]));
+    }
+}
+
+static void glue(blizzard_draw_line24mode2_, DEPTH)(PIXEL_TYPE *dest,
+                const uint8_t *src, unsigned int width)
+{
+    unsigned int r, g, b;
+    const uint8_t *end = src + width;
+    while (src < end) {
+        r = *src ++;
+        src ++;
+        b = *src ++;
+        g = *src ++;
+        COPY_PIXEL1(dest, glue(rgb_to_pixel, DEPTH)(r, g, b));
+    }
+}
+
+/* No rotation */
+static blizzard_fn_t glue(blizzard_draw_fn_, DEPTH)[0x10] = {
+    NULL,
+    /* RGB 5:6:5*/
+    (blizzard_fn_t) glue(blizzard_draw_line16_, DEPTH),
+    /* RGB 6:6:6 mode 1 */
+    (blizzard_fn_t) glue(blizzard_draw_line24mode1_, DEPTH),
+    /* RGB 8:8:8 mode 1 */
+    (blizzard_fn_t) glue(blizzard_draw_line24mode1_, DEPTH),
+    NULL, NULL,
+    /* RGB 6:6:6 mode 2 */
+    (blizzard_fn_t) glue(blizzard_draw_line24mode2_, DEPTH),
+    /* RGB 8:8:8 mode 2 */
+    (blizzard_fn_t) glue(blizzard_draw_line24mode2_, DEPTH),
+    /* YUV 4:2:2 */
+    NULL,
+    /* YUV 4:2:0 */
+    NULL,
+    NULL, NULL, NULL, NULL, NULL, NULL,
+};
+
+/* 90deg, 180deg and 270deg rotation */
+static blizzard_fn_t glue(blizzard_draw_fn_r_, DEPTH)[0x10] = {
+    /* TODO */
+    [0 ... 0xf] = NULL,
+};
+
+#undef DEPTH
+#undef SKIP_PIXEL
+#undef COPY_PIXEL
+#undef COPY_PIXEL1
+#undef PIXEL_TYPE
+
+#undef SWAP_WORDS
diff --git a/hw/boards.h b/hw/boards.h
index affcaa6..408d1e8 100644
--- a/hw/boards.h
+++ b/hw/boards.h
@@ -80,6 +80,9 @@ extern QEMUMachine terrierpda_machine;
 /* palm.c */
 extern QEMUMachine palmte_machine;
 
+/* nseries.c */
+extern QEMUMachine n800_machine;
+
 /* gumstix.c */
 extern QEMUMachine connex_machine;
 extern QEMUMachine verdex_machine;
diff --git a/hw/cbus.c b/hw/cbus.c
new file mode 100644
index 0000000..001b007
--- /dev/null
+++ b/hw/cbus.c
@@ -0,0 +1,565 @@
+/*
+ * CBUS three-pin bus and the Retu / Betty / Tahvo / Vilma / Avilma /
+ * Hinku / Vinku / Ahne / Pihi chips used in various Nokia platforms.
+ *
+ * Copyright (C) 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 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include "qemu-common.h"
+#include "irq.h"
+#include "devices.h"
+#include "sysemu.h"
+
+//#define DEBUG
+
+struct cbus_slave_s;
+struct cbus_priv_s {
+    struct cbus_s cbus;
+
+    int sel;
+    int dat;
+    int clk;
+    int bit;
+    int dir;
+    uint16_t val;
+    qemu_irq dat_out;
+
+    int addr;
+    int reg;
+    int rw;
+    enum {
+        cbus_address,
+        cbus_value,
+    } cycle;
+
+    struct cbus_slave_s *slave[8];
+};
+
+struct cbus_slave_s {
+    void *opaque;
+    void (*io)(void *opaque, int rw, int reg, uint16_t *val);
+    int addr;
+};
+
+static void cbus_io(struct cbus_priv_s *s)
+{
+    if (s->slave[s->addr])
+        s->slave[s->addr]->io(s->slave[s->addr]->opaque,
+                        s->rw, s->reg, &s->val);
+    else
+        cpu_abort(cpu_single_env, "%s: bad slave address %i\n",
+                        __FUNCTION__, s->addr);
+}
+
+static void cbus_cycle(struct cbus_priv_s *s)
+{
+    switch (s->cycle) {
+    case cbus_address:
+        s->addr = (s->val >> 6) & 7;
+        s->rw =   (s->val >> 5) & 1;
+        s->reg =  (s->val >> 0) & 0x1f;
+
+        s->cycle = cbus_value;
+        s->bit = 15;
+        s->dir = !s->rw;
+        s->val = 0;
+
+        if (s->rw)
+            cbus_io(s);
+        break;
+
+    case cbus_value:
+        if (!s->rw)
+            cbus_io(s);
+
+        s->cycle = cbus_address;
+        s->bit = 8;
+        s->dir = 1;
+        s->val = 0;
+        break;
+    }
+}
+
+static void cbus_clk(void *opaque, int line, int level)
+{
+    struct cbus_priv_s *s = (struct cbus_priv_s *) opaque;
+
+    if (!s->sel && level && !s->clk) {
+        if (s->dir)
+            s->val |= s->dat << (s->bit --);
+        else
+            qemu_set_irq(s->dat_out, (s->val >> (s->bit --)) & 1);
+
+        if (s->bit < 0)
+            cbus_cycle(s);
+    }
+
+    s->clk = level;
+}
+
+static void cbus_dat(void *opaque, int line, int level)
+{
+    struct cbus_priv_s *s = (struct cbus_priv_s *) opaque;
+
+    s->dat = level;
+}
+
+static void cbus_sel(void *opaque, int line, int level)
+{
+    struct cbus_priv_s *s = (struct cbus_priv_s *) opaque;
+
+    if (!level) {
+        s->dir = 1;
+        s->bit = 8;
+        s->val = 0;
+    }
+
+    s->sel = level;
+}
+
+struct cbus_s *cbus_init(qemu_irq dat)
+{
+    struct cbus_priv_s *s = (struct cbus_priv_s *) qemu_mallocz(sizeof(*s));
+
+    s->dat_out = dat;
+    s->cbus.clk = qemu_allocate_irqs(cbus_clk, s, 1)[0];
+    s->cbus.dat = qemu_allocate_irqs(cbus_dat, s, 1)[0];
+    s->cbus.sel = qemu_allocate_irqs(cbus_sel, s, 1)[0];
+
+    s->sel = 1;
+    s->clk = 0;
+    s->dat = 0;
+
+    return &s->cbus;
+}
+
+void cbus_attach(struct cbus_s *bus, void *slave_opaque)
+{
+    struct cbus_slave_s *slave = (struct cbus_slave_s *) slave_opaque;
+    struct cbus_priv_s *s = (struct cbus_priv_s *) bus;
+
+    s->slave[slave->addr] = slave;
+}
+
+/* Retu/Vilma */
+struct cbus_retu_s {
+    uint16_t irqst;
+    uint16_t irqen;
+    uint16_t cc[2];
+    int channel;
+    uint16_t result[16];
+    uint16_t sample;
+
+    struct {
+        uint16_t cal;
+    } rtc;
+
+    int is_vilma;
+    qemu_irq irq;
+    struct cbus_slave_s cbus;
+};
+
+static void retu_interrupt_update(struct cbus_retu_s *s)
+{
+    qemu_set_irq(s->irq, s->irqst & ~s->irqen);
+}
+
+#define RETU_REG_ASICR		0x00	/* (RO) ASIC ID & revision */
+#define RETU_REG_IDR		0x01	/* (T)  Interrupt ID */
+#define RETU_REG_IMR		0x02	/* (RW) Interrupt mask */
+#define RETU_REG_RTCDSR		0x03	/* (RW) RTC seconds register */
+#define RETU_REG_RTCHMR		0x04	/* (RO) RTC hours and minutes reg */
+#define RETU_REG_RTCHMAR	0x05	/* (RW) RTC hours and minutes set reg */
+#define RETU_REG_RTCCALR	0x06	/* (RW) RTC calibration register */
+#define RETU_REG_ADCR		0x08	/* (RW) ADC result register */
+#define RETU_REG_ADCSCR		0x09	/* (RW) ADC sample control register */
+#define RETU_REG_AFCR		0x0a	/* (RW) AFC register */
+#define RETU_REG_ANTIFR		0x0b	/* (RW) AntiF register */
+#define RETU_REG_CALIBR		0x0c	/* (RW) CalibR register*/
+#define RETU_REG_CCR1		0x0d	/* (RW) Common control register 1 */
+#define RETU_REG_CCR2		0x0e	/* (RW) Common control register 2 */
+#define RETU_REG_RCTRL_CLR	0x0f	/* (T)  Regulator clear register */
+#define RETU_REG_RCTRL_SET	0x10	/* (T)  Regulator set register */
+#define RETU_REG_TXCR		0x11	/* (RW) TxC register */
+#define RETU_REG_STATUS		0x16	/* (RO) Status register */
+#define RETU_REG_WATCHDOG	0x17	/* (RW) Watchdog register */
+#define RETU_REG_AUDTXR		0x18	/* (RW) Audio Codec Tx register */
+#define RETU_REG_AUDPAR		0x19	/* (RW) AudioPA register */
+#define RETU_REG_AUDRXR1	0x1a	/* (RW) Audio receive register 1 */
+#define RETU_REG_AUDRXR2	0x1b	/* (RW) Autio receive register 2 */
+#define RETU_REG_SGR1		0x1c	/* (RW) */
+#define RETU_REG_SCR1		0x1d	/* (RW) */
+#define RETU_REG_SGR2		0x1e	/* (RW) */
+#define RETU_REG_SCR2		0x1f	/* (RW) */
+
+/* Retu Interrupt sources */
+enum {
+    retu_int_pwr	= 0,	/* Power */
+    retu_int_char	= 1,	/* Charger */
+    retu_int_rtcs	= 2,	/* Seconds */
+    retu_int_rtcm	= 3,	/* Minutes */
+    retu_int_rtcd	= 4,	/* Days */
+    retu_int_rtca	= 5,	/* Alarm */
+    retu_int_hook	= 6,	/* Hook */
+    retu_int_head	= 7,	/* Headset */
+    retu_int_adcs	= 8,	/* ADC sample */
+};
+
+/* Retu ADC channel wiring */
+enum {
+    retu_adc_bsi	= 1,	/* BSI */
+    retu_adc_batt_temp	= 2,	/* Battery temperature */
+    retu_adc_chg_volt	= 3,	/* Charger voltage */
+    retu_adc_head_det	= 4,	/* Headset detection */
+    retu_adc_hook_det	= 5,	/* Hook detection */
+    retu_adc_rf_gp	= 6,	/* RF GP */
+    retu_adc_tx_det	= 7,	/* Wideband Tx detection */
+    retu_adc_batt_volt	= 8,	/* Battery voltage */
+    retu_adc_sens	= 10,	/* Light sensor */
+    retu_adc_sens_temp	= 11,	/* Light sensor temperature */
+    retu_adc_bbatt_volt	= 12,	/* Backup battery voltage */
+    retu_adc_self_temp	= 13,	/* RETU temperature */
+};
+
+static inline uint16_t retu_read(struct cbus_retu_s *s, int reg)
+{
+#ifdef DEBUG
+    printf("RETU read at %02x\n", reg);
+#endif
+
+    switch (reg) {
+    case RETU_REG_ASICR:
+        return 0x0015 | (s->is_vilma << 7);
+
+    case RETU_REG_IDR:
+        return s->irqst;
+
+    case RETU_REG_IMR:
+        return s->irqen;
+
+    case RETU_REG_RTCDSR:
+    case RETU_REG_RTCHMR:
+    case RETU_REG_RTCHMAR:
+        /* TODO */
+        return 0x0000;
+
+    case RETU_REG_RTCCALR:
+        return s->rtc.cal;
+
+    case RETU_REG_ADCR:
+        return (s->channel << 10) | s->result[s->channel];
+    case RETU_REG_ADCSCR:
+        return s->sample;
+
+    case RETU_REG_AFCR:
+    case RETU_REG_ANTIFR:
+    case RETU_REG_CALIBR:
+        /* TODO */
+        return 0x0000;
+
+    case RETU_REG_CCR1:
+        return s->cc[0];
+    case RETU_REG_CCR2:
+        return s->cc[1];
+
+    case RETU_REG_RCTRL_CLR:
+    case RETU_REG_RCTRL_SET:
+    case RETU_REG_TXCR:
+    case RETU_REG_STATUS:
+    case RETU_REG_WATCHDOG:
+    case RETU_REG_AUDTXR:
+    case RETU_REG_AUDPAR:
+    case RETU_REG_AUDRXR1:
+    case RETU_REG_AUDRXR2:
+    case RETU_REG_SGR1:
+    case RETU_REG_SCR1:
+    case RETU_REG_SGR2:
+    case RETU_REG_SCR2:
+        /* TODO */
+        return 0x0000;
+
+    default:
+        cpu_abort(cpu_single_env, "%s: bad register %02x\n",
+                        __FUNCTION__, reg);
+    }
+}
+
+static inline void retu_write(struct cbus_retu_s *s, int reg, uint16_t val)
+{
+#ifdef DEBUG
+    printf("RETU write of %04x at %02x\n", val, reg);
+#endif
+
+    switch (reg) {
+    case RETU_REG_IDR:
+        s->irqst ^= val;
+        retu_interrupt_update(s);
+        break;
+
+    case RETU_REG_IMR:
+        s->irqen = val;
+        retu_interrupt_update(s);
+        break;
+
+    case RETU_REG_RTCDSR:
+    case RETU_REG_RTCHMAR:
+        /* TODO */
+        break;
+
+    case RETU_REG_RTCCALR:
+        s->rtc.cal = val;
+        break;
+
+    case RETU_REG_ADCR:
+        s->channel = (val >> 10) & 0xf;
+        s->irqst |= 1 << retu_int_adcs;
+        retu_interrupt_update(s);
+        break;
+    case RETU_REG_ADCSCR:
+        s->sample &= ~val;
+        break;
+
+    case RETU_REG_AFCR:
+    case RETU_REG_ANTIFR:
+    case RETU_REG_CALIBR:
+
+    case RETU_REG_CCR1:
+        s->cc[0] = val;
+        break;
+    case RETU_REG_CCR2:
+        s->cc[1] = val;
+
+        break;
+    case RETU_REG_RCTRL_CLR:
+    case RETU_REG_RCTRL_SET:
+    case RETU_REG_STATUS:
+        /* TODO */
+        break;
+
+    case RETU_REG_WATCHDOG:
+        if (val == 0 && (s->cc[0] & 2))
+            qemu_system_shutdown_request();
+        break;
+
+    case RETU_REG_TXCR:
+    case RETU_REG_AUDTXR:
+    case RETU_REG_AUDPAR:
+    case RETU_REG_AUDRXR1:
+    case RETU_REG_AUDRXR2:
+    case RETU_REG_SGR1:
+    case RETU_REG_SCR1:
+    case RETU_REG_SGR2:
+    case RETU_REG_SCR2:
+        /* TODO */
+        break;
+
+    default:
+        cpu_abort(cpu_single_env, "%s: bad register %02x\n",
+                        __FUNCTION__, reg);
+    }
+}
+
+static void retu_io(void *opaque, int rw, int reg, uint16_t *val)
+{
+    struct cbus_retu_s *s = (struct cbus_retu_s *) opaque;
+
+    if (rw)
+        *val = retu_read(s, reg);
+    else
+        retu_write(s, reg, *val);
+}
+
+void *retu_init(qemu_irq irq, int vilma)
+{
+    struct cbus_retu_s *s = (struct cbus_retu_s *) qemu_mallocz(sizeof(*s));
+
+    s->irq = irq;
+    s->irqen = 0xffff;
+    s->irqst = 0x0000;
+    s->is_vilma = !!vilma;
+    s->rtc.cal = 0x01;
+    s->result[retu_adc_bsi] = 0x100;
+    s->result[retu_adc_batt_temp] = 0x100;
+    s->result[retu_adc_chg_volt] = 0x200;
+    s->result[retu_adc_batt_volt] = 0x240;
+    s->result[retu_adc_sens] = 0x100;
+    s->result[retu_adc_sens_temp] = 0x100;
+    s->result[retu_adc_bbatt_volt] = 0x200;
+    s->result[retu_adc_self_temp] = 0x100;
+
+    s->cbus.opaque = s;
+    s->cbus.io = retu_io;
+    s->cbus.addr = 1;
+
+    return &s->cbus;
+}
+
+/* Tahvo/Betty */
+struct cbus_tahvo_s {
+    uint16_t irqst;
+    uint16_t irqen;
+    uint8_t charger;
+    uint8_t backlight;
+    uint16_t usbr;
+    uint16_t power;
+
+    int is_betty;
+    qemu_irq irq;
+    struct cbus_slave_s cbus;
+};
+
+static void tahvo_interrupt_update(struct cbus_tahvo_s *s)
+{
+    qemu_set_irq(s->irq, s->irqst & ~s->irqen);
+}
+
+#define TAHVO_REG_ASICR		0x00	/* (RO) ASIC ID & revision */
+#define TAHVO_REG_IDR		0x01	/* (T)  Interrupt ID */
+#define TAHVO_REG_IDSR		0x02	/* (RO) Interrupt status */
+#define TAHVO_REG_IMR		0x03	/* (RW) Interrupt mask */
+#define TAHVO_REG_CHAPWMR	0x04	/* (RW) Charger PWM */
+#define TAHVO_REG_LEDPWMR	0x05	/* (RW) LED PWM */
+#define TAHVO_REG_USBR		0x06	/* (RW) USB control */
+#define TAHVO_REG_RCR		0x07	/* (RW) Some kind of power management */
+#define TAHVO_REG_CCR1		0x08	/* (RW) Common control register 1 */
+#define TAHVO_REG_CCR2		0x09	/* (RW) Common control register 2 */
+#define TAHVO_REG_TESTR1	0x0a	/* (RW) Test register 1 */
+#define TAHVO_REG_TESTR2	0x0b	/* (RW) Test register 2 */
+#define TAHVO_REG_NOPR		0x0c	/* (RW) Number of periods */
+#define TAHVO_REG_FRR		0x0d	/* (RO) FR */
+
+static inline uint16_t tahvo_read(struct cbus_tahvo_s *s, int reg)
+{
+#ifdef DEBUG
+    printf("TAHVO read at %02x\n", reg);
+#endif
+
+    switch (reg) {
+    case TAHVO_REG_ASICR:
+        return 0x0021 | (s->is_betty ? 0x0b00 : 0x0300);
+
+    case TAHVO_REG_IDR:
+    case TAHVO_REG_IDSR:	/* XXX: what does this do?  */
+        return s->irqst;
+
+    case TAHVO_REG_IMR:
+        return s->irqen;
+
+    case TAHVO_REG_CHAPWMR:
+        return s->charger;
+
+    case TAHVO_REG_LEDPWMR:
+        return s->backlight;
+
+    case TAHVO_REG_USBR:
+        return s->usbr;
+
+    case TAHVO_REG_RCR:
+        return s->power;
+
+    case TAHVO_REG_CCR1:
+    case TAHVO_REG_CCR2:
+    case TAHVO_REG_TESTR1:
+    case TAHVO_REG_TESTR2:
+    case TAHVO_REG_NOPR:
+    case TAHVO_REG_FRR:
+        return 0x0000;
+
+    default:
+        cpu_abort(cpu_single_env, "%s: bad register %02x\n",
+                        __FUNCTION__, reg);
+    }
+}
+
+static inline void tahvo_write(struct cbus_tahvo_s *s, int reg, uint16_t val)
+{
+#ifdef DEBUG
+    printf("TAHVO write of %04x at %02x\n", val, reg);
+#endif
+
+    switch (reg) {
+    case TAHVO_REG_IDR:
+        s->irqst ^= val;
+        tahvo_interrupt_update(s);
+        break;
+
+    case TAHVO_REG_IMR:
+        s->irqen = val;
+        tahvo_interrupt_update(s);
+        break;
+
+    case TAHVO_REG_CHAPWMR:
+        s->charger = val;
+        break;
+
+    case TAHVO_REG_LEDPWMR:
+        if (s->backlight != (val & 0x7f)) {
+            s->backlight = val & 0x7f;
+            printf("%s: LCD backlight now at %i / 127\n",
+                            __FUNCTION__, s->backlight);
+        }
+        break;
+
+    case TAHVO_REG_USBR:
+        s->usbr = val;
+        break;
+
+    case TAHVO_REG_RCR:
+        s->power = val;
+        break;
+
+    case TAHVO_REG_CCR1:
+    case TAHVO_REG_CCR2:
+    case TAHVO_REG_TESTR1:
+    case TAHVO_REG_TESTR2:
+    case TAHVO_REG_NOPR:
+    case TAHVO_REG_FRR:
+        break;
+
+    default:
+        cpu_abort(cpu_single_env, "%s: bad register %02x\n",
+                        __FUNCTION__, reg);
+    }
+}
+
+static void tahvo_io(void *opaque, int rw, int reg, uint16_t *val)
+{
+    struct cbus_tahvo_s *s = (struct cbus_tahvo_s *) opaque;
+
+    if (rw)
+        *val = tahvo_read(s, reg);
+    else
+        tahvo_write(s, reg, *val);
+}
+
+void *tahvo_init(qemu_irq irq, int betty)
+{
+    struct cbus_tahvo_s *s = (struct cbus_tahvo_s *) qemu_mallocz(sizeof(*s));
+
+    s->irq = irq;
+    s->irqen = 0xffff;
+    s->irqst = 0x0000;
+    s->is_betty = !!betty;
+
+    s->cbus.opaque = s;
+    s->cbus.io = tahvo_io;
+    s->cbus.addr = 2;
+
+    return &s->cbus;
+}
diff --git a/hw/devices.h b/hw/devices.h
index 07c673b..6f1d27b 100644
--- a/hw/devices.h
+++ b/hw/devices.h
@@ -16,7 +16,38 @@ uint32_t ads7846_read(void *opaque);
 void ads7846_write(void *opaque, uint32_t value);
 struct ads7846_state_s *ads7846_init(qemu_irq penirq);
 
+/* tsc210x.c */
+struct uwire_slave_s;
+struct mouse_transform_info_s;
+struct uwire_slave_s *tsc2102_init(qemu_irq pint, AudioState *audio);
+struct uwire_slave_s *tsc2301_init(qemu_irq penirq, qemu_irq kbirq,
+                qemu_irq dav, AudioState *audio);
+struct i2s_codec_s *tsc210x_codec(struct uwire_slave_s *chip);
+uint32_t tsc210x_txrx(void *opaque, uint32_t value);
+void tsc210x_set_transform(struct uwire_slave_s *chip,
+                struct mouse_transform_info_s *info);
+void tsc210x_key_event(struct uwire_slave_s *chip, int key, int down);
+
 /* stellaris_input.c */
 void stellaris_gamepad_init(int n, qemu_irq *irq, const int *keycode);
 
+/* blizzard.c */
+void *s1d13745_init(qemu_irq gpio_int, DisplayState *ds);
+void s1d13745_write(void *opaque, int dc, uint16_t value);
+void s1d13745_write_block(void *opaque, int dc,
+                void *buf, size_t len, int pitch);
+uint16_t s1d13745_read(void *opaque, int dc);
+
+/* cbus.c */
+struct cbus_s {
+    qemu_irq clk;
+    qemu_irq dat;
+    qemu_irq sel;
+};
+struct cbus_s *cbus_init(qemu_irq dat_out);
+void cbus_attach(struct cbus_s *bus, void *slave_opaque);
+
+void *retu_init(qemu_irq irq, int vilma);
+void *tahvo_init(qemu_irq irq, int betty);
+
 #endif
diff --git a/hw/flash.h b/hw/flash.h
index 42d25fe..c000d33 100644
--- a/hw/flash.h
+++ b/hw/flash.h
@@ -34,6 +34,11 @@ uint8_t nand_getio(struct nand_flash_s *s);
 #define NAND_MFR_HYNIX		0xad
 #define NAND_MFR_MICRON		0x2c
 
+/* onenand.c */
+void onenand_base_update(void *opaque, target_phys_addr_t new);
+void onenand_base_unmap(void *opaque);
+void *onenand_init(uint32_t id, int regshift, qemu_irq irq);
+
 /* ecc.c */
 struct ecc_state_s {
     uint8_t cp;		/* Column parity */
diff --git a/hw/i2c.h b/hw/i2c.h
index 2897036..fae46b7 100644
--- a/hw/i2c.h
+++ b/hw/i2c.h
@@ -71,4 +71,14 @@ uint32_t wm8750_adc_dat(void *opaque);
 /* ssd0303.c */
 void ssd0303_init(DisplayState *ds, i2c_bus *bus, int address);
 
+/* twl92230.c */
+i2c_slave *twl92230_init(i2c_bus *bus, qemu_irq irq);
+qemu_irq *twl92230_gpio_in_get(i2c_slave *i2c);
+void twl92230_gpio_out_set(i2c_slave *i2c, int line, qemu_irq handler);
+
+/* tmp105.c */
+struct i2c_slave *tmp105_init(i2c_bus *bus, qemu_irq alarm);
+void tmp105_reset(i2c_slave *i2c);
+void tmp105_set(i2c_slave *i2c, int temp);
+
 #endif
diff --git a/hw/integratorcp.c b/hw/integratorcp.c
index 549cc25..f6e6364 100644
--- a/hw/integratorcp.c
+++ b/hw/integratorcp.c
@@ -469,6 +469,11 @@ static void icp_control_init(uint32_t base)
 
 /* Board init.  */
 
+static struct arm_boot_info integrator_binfo = {
+    .loader_start = 0x0,
+    .board_id = 0x113,
+};
+
 static void integratorcp_init(int ram_size, int vga_ram_size,
                      const char *boot_device, DisplayState *ds,
                      const char *kernel_filename, const char *kernel_cmdline,
@@ -527,8 +532,11 @@ static void integratorcp_init(int ram_size, int vga_ram_size,
     }
     pl110_init(ds, 0xc0000000, pic[22], 0);
 
-    arm_load_kernel(env, ram_size, kernel_filename, kernel_cmdline,
-                    initrd_filename, 0x113, 0x0);
+    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(env, &integrator_binfo);
 }
 
 QEMUMachine integratorcp_machine = {
diff --git a/hw/mainstone.c b/hw/mainstone.c
index 5856791..9564fc3 100644
--- a/hw/mainstone.c
+++ b/hw/mainstone.c
@@ -59,12 +59,17 @@ static struct keymap map[0xE0] = {
 
 enum mainstone_model_e { mainstone };
 
+static struct arm_boot_info mainstone_binfo = {
+    .loader_start = PXA2XX_SDRAM_BASE,
+    .ram_size = 0x04000000,
+};
+
 static void mainstone_common_init(int ram_size, int vga_ram_size,
                 DisplayState *ds, const char *kernel_filename,
                 const char *kernel_cmdline, const char *initrd_filename,
                 const char *cpu_model, enum mainstone_model_e model, int arm_id)
 {
-    uint32_t mainstone_ram   = 0x04000000;
+    uint32_t mainstone_ram   = mainstone_binfo.ram_size;
     uint32_t mainstone_rom   = 0x00800000;
     uint32_t mainstone_flash = 0x02000000;
     uint32_t sector_len = 256 * 1024;
@@ -90,7 +95,7 @@ static void mainstone_common_init(int ram_size, int vga_ram_size,
                     qemu_ram_alloc(mainstone_rom) | IO_MEM_ROM);
 
     /* Setup initial (reset) machine state */
-    cpu->env->regs[15] = PXA2XX_SDRAM_BASE;
+    cpu->env->regs[15] = mainstone_binfo.loader_start;
 
     /* There are two 32MiB flash devices on the board */
     for (i = 0; i < 2; i ++) {
@@ -121,8 +126,11 @@ static void mainstone_common_init(int ram_size, int vga_ram_size,
 
     smc91c111_init(&nd_table[0], MST_ETH_PHYS, mst_irq[ETHERNET_IRQ]);
 
-    arm_load_kernel(cpu->env, mainstone_ram, kernel_filename, kernel_cmdline,
-                    initrd_filename, arm_id, PXA2XX_SDRAM_BASE);
+    mainstone_binfo.kernel_filename = kernel_filename;
+    mainstone_binfo.kernel_cmdline = kernel_cmdline;
+    mainstone_binfo.initrd_filename = initrd_filename;
+    mainstone_binfo.board_id = arm_id;
+    arm_load_kernel(cpu->env, &mainstone_binfo);
 }
 
 static void mainstone_init(int ram_size, int vga_ram_size,
diff --git a/hw/max7310.c b/hw/max7310.c
index 75e56c7..397950a 100644
--- a/hw/max7310.c
+++ b/hw/max7310.c
@@ -134,8 +134,8 @@ static void max7310_event(i2c_slave *i2c, enum i2c_event event)
         s->i2c_command_byte = 1;
         break;
     case I2C_FINISH:
-        if (s->len == 1)
 #ifdef VERBOSE
+        if (s->len == 1)
             printf("%s: message too short (%i bytes)\n", __FUNCTION__, s->len);
 #endif
         break;
diff --git a/hw/nseries.c b/hw/nseries.c
new file mode 100644
index 0000000..0425d46
--- /dev/null
+++ b/hw/nseries.c
@@ -0,0 +1,870 @@
+/*
+ * 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 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include "qemu-common.h"
+#include "sysemu.h"
+#include "omap.h"
+#include "arm-misc.h"
+#include "irq.h"
+#include "console.h"
+#include "boards.h"
+#include "i2c.h"
+#include "devices.h"
+#include "flash.h"
+#include "hw.h"
+
+/* Nokia N800 support */
+struct n800_s {
+    struct omap_mpu_state_s *cpu;
+
+    struct rfbi_chip_s blizzard;
+    struct uwire_slave_s *ts;
+    i2c_bus *i2c;
+
+    int keymap[0x80];
+};
+
+#define N800_MMC2_WP_GPIO		8
+#define N800_CAM_TURN_GPIO		12
+#define N800_BLIZZARD_POWERDOWN_GPIO	15
+#define N800_MMC1_WP_GPIO		23
+#define N800_ONENAND_GPIO		26
+#define N800_BT_WKUP_GPIO		61
+#define N800_STI_GPIO			62
+#define N800_CBUS_SEL_GPIO		64
+#define N800_CBUS_CLK_GPIO		65
+#define N800_CBUS_DAT_GPIO		66
+#define N800_WLAN_IRQ_GPIO		87
+#define N800_BT_RESET_GPIO		92
+#define N800_TEA5761_CS_GPIO		93
+#define N800_UNKNOWN_GPIO		94
+#define N800_CAM_ACT_GPIO		95
+#define N800_MMC_CS_GPIO		96
+#define N800_WLAN_PWR_GPIO		97
+#define N800_BT_HOST_WKUP_GPIO		98
+#define N800_TSC_TS_GPIO		103
+#define N800_HEADPHONE_GPIO		107
+#define N800_RETU_GPIO			108
+#define N800_TSC_KP_IRQ_GPIO		109
+#define N800_BAT_COVER_GPIO		110
+#define N800_TAHVO_GPIO			111
+#define N800_TSC_RESET_GPIO		119
+#define N800_TMP105_GPIO		125
+
+#define XLDR_LL_UART			1
+
+#define N800_TMP105_ADDR		0x48
+#define N800_MENELAUS_ADDR		0x72
+
+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 n800_gpio_setup(struct n800_s *s)
+{
+    qemu_irq *mmc_cs = qemu_allocate_irqs(n800_mmc_cs_cb, s->cpu->mmc, 1);
+    omap2_gpio_out_set(s->cpu->gpif, N800_MMC_CS_GPIO, mmc_cs[0]);
+
+    qemu_irq_lower(omap2_gpio_in_get(s->cpu->gpif, N800_BAT_COVER_GPIO)[0]);
+}
+
+static void n800_nand_setup(struct n800_s *s)
+{
+    /* Either ec40xx or ec48xx are OK for the ID */
+    omap_gpmc_attach(s->cpu->gpmc, 0, 0, onenand_base_update,
+                    onenand_base_unmap,
+                    onenand_init(0xec4800, 1,
+                            omap2_gpio_in_get(s->cpu->gpif,
+                                    N800_ONENAND_GPIO)[0]));
+}
+
+static void n800_i2c_setup(struct n800_s *s)
+{
+    qemu_irq tmp_irq = omap2_gpio_in_get(s->cpu->gpif, N800_TMP105_GPIO)[0];
+
+    /* Attach the CPU on one end of our I2C bus.  */
+    s->i2c = omap_i2c_bus(s->cpu->i2c[0]);
+
+    /* Attach a menelaus PM chip */
+    i2c_set_slave_address(
+                    twl92230_init(s->i2c,
+                            s->cpu->irq[0][OMAP_INT_24XX_SYS_NIRQ]),
+                    N800_MENELAUS_ADDR);
+
+    /* Attach a TMP105 PM chip (A0 wired to ground) */
+    i2c_set_slave_address(tmp105_init(s->i2c, tmp_irq), N800_TMP105_ADDR);
+}
+
+/* Touchscreen and keypad controller */
+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)
+        return;
+
+    tsc210x_key_event(s->ts, 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,	/* FS (F6) */
+    65,	/* Zoom+ (F7) */
+    -1,
+};
+
+static struct mouse_transform_info_s n800_pointercal = {
+    .x = 800,
+    .y = 480,
+    .a = { 14560, -68, -3455208, -39, -9621, 35152972, 65536 },
+};
+
+static void n800_tsc_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 = 0;	/* NC */
+    qemu_irq kbirq = omap2_gpio_in_get(s->cpu->gpif, N800_TSC_KP_IRQ_GPIO)[0];
+    qemu_irq dav = omap2_gpio_in_get(s->cpu->gpif, N800_TSC_TS_GPIO)[0];
+
+    s->ts = tsc2301_init(penirq, kbirq, dav, 0);
+
+    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, &n800_pointercal);
+}
+
+/* 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)
+{
+    struct mipid_s *s = (struct mipid_s *) opaque;
+    uint8_t ret;
+
+    if (s->p >= sizeof(s->resp) / sizeof(*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 *) qemu_mallocz(sizeof(*s));
+
+    s->id = 0x838f03;
+    mipid_reset(s);
+
+    return s;
+}
+
+static void n800_spi_setup(struct n800_s *s)
+{
+    void *tsc2301 = s->ts->opaque;
+    void *mipid = mipid_init();
+
+    omap_mcspi_attach(s->cpu->mcspi[0], tsc210x_txrx, tsc2301, 0);
+    omap_mcspi_attach(s->cpu->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)
+{
+    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 */
+}
+
+static void n800_dss_setup(struct n800_s *s, DisplayState *ds)
+{
+    s->blizzard.opaque = s1d13745_init(0, ds);
+    s->blizzard.block = s1d13745_write_block;
+    s->blizzard.write = s1d13745_write;
+    s->blizzard.read = s1d13745_read;
+
+    omap_rfbi_attach(s->cpu->dss, 0, &s->blizzard);
+}
+
+static void n800_cbus_setup(struct n800_s *s)
+{
+    qemu_irq dat_out = omap2_gpio_in_get(s->cpu->gpif, N800_CBUS_DAT_GPIO)[0];
+    qemu_irq retu_irq = omap2_gpio_in_get(s->cpu->gpif, N800_RETU_GPIO)[0];
+    qemu_irq tahvo_irq = omap2_gpio_in_get(s->cpu->gpif, N800_TAHVO_GPIO)[0];
+
+    struct cbus_s *cbus = cbus_init(dat_out);
+
+    omap2_gpio_out_set(s->cpu->gpif, N800_CBUS_CLK_GPIO, cbus->clk);
+    omap2_gpio_out_set(s->cpu->gpif, N800_CBUS_DAT_GPIO, cbus->dat);
+    omap2_gpio_out_set(s->cpu->gpif, N800_CBUS_SEL_GPIO, cbus->sel);
+
+    cbus_attach(cbus, retu_init(retu_irq, 1));
+    cbus_attach(cbus, tahvo_init(tahvo_irq, 1));
+}
+
+/* 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));
+}
+
+#if 0
+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,
+};
+#endif
+
+/* Setup sequence done by the bootloader */
+static void n800_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->cpu->env->regs[15] = s->cpu->env->boot_info->loader_start;
+}
+
+#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 int n800_atag_setup(struct arm_boot_info *info, void *p)
+{
+    uint8_t *b;
+    uint16_t *w;
+    uint32_t *l;
+
+    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 ++;
+
+    stw_raw(w ++, OMAP_TAG_EM_ASIC_BB5);	/* u16 tag */
+    stw_raw(w ++, 4);				/* u16 len */
+    stw_raw(w ++, N800_RETU_GPIO);		/* s16 retu_irq_gpio */
+    stw_raw(w ++, N800_TAHVO_GPIO);		/* s16 tahvo_irq_gpio */
+
+    stw_raw(w ++, OMAP_TAG_CBUS);		/* u16 tag */
+    stw_raw(w ++, 8);				/* u16 len */
+    stw_raw(w ++, N800_CBUS_CLK_GPIO);		/* s16 clk_gpio */
+    stw_raw(w ++, N800_CBUS_DAT_GPIO);		/* s16 dat_gpio */
+    stw_raw(w ++, N800_CBUS_SEL_GPIO);		/* s16 sel_gpio */
+    w ++;
+
+    stw_raw(w ++, OMAP_TAG_GPIO_SWITCH);	/* u16 tag */
+    stw_raw(w ++, 20);				/* u16 len */
+    strcpy((void *) w, "bat_cover");		/* char name[12] */
+    w += 6;
+    stw_raw(w ++, N800_BAT_COVER_GPIO);		/* u16 gpio */
+    stw_raw(w ++, 0x01);
+    stw_raw(w ++, 0);
+    stw_raw(w ++, 0);
+
+    stw_raw(w ++, OMAP_TAG_GPIO_SWITCH);	/* u16 tag */
+    stw_raw(w ++, 20);				/* u16 len */
+    strcpy((void *) w, "cam_act");		/* char name[12] */
+    w += 6;
+    stw_raw(w ++, N800_CAM_ACT_GPIO);		/* u16 gpio */
+    stw_raw(w ++, 0x20);
+    stw_raw(w ++, 0);
+    stw_raw(w ++, 0);
+
+    stw_raw(w ++, OMAP_TAG_GPIO_SWITCH);	/* u16 tag */
+    stw_raw(w ++, 20);				/* u16 len */
+    strcpy((void *) w, "cam_turn");		/* char name[12] */
+    w += 6;
+    stw_raw(w ++, N800_CAM_TURN_GPIO);		/* u16 gpio */
+    stw_raw(w ++, 0x21);
+    stw_raw(w ++, 0);
+    stw_raw(w ++, 0);
+
+    stw_raw(w ++, OMAP_TAG_GPIO_SWITCH);	/* u16 tag */
+    stw_raw(w ++, 20);				/* u16 len */
+    strcpy((void *) w, "headphone");		/* char name[12] */
+    w += 6;
+    stw_raw(w ++, N800_HEADPHONE_GPIO);		/* u16 gpio */
+    stw_raw(w ++, 0x11);
+    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 ++, N800_BT_WKUP_GPIO);		/* u8 bt_wakeup_gpio */
+    stb_raw(b ++, N800_BT_HOST_WKUP_GPIO);	/* u8 host_wakeup_gpio */
+    stb_raw(b ++, N800_BT_RESET_GPIO);		/* u8 reset_gpio */
+    stb_raw(b ++, 1);				/* u8 bt_uart */
+    memset(b, 0, 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 ++, N800_WLAN_PWR_GPIO);		/* s16 power_gpio */
+    stw_raw(w ++, N800_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 */
+    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 ++, N800_TEA5761_CS_GPIO);	/* u16 enable_gpio */
+    w ++;
+
+    stw_raw(w ++, OMAP_TAG_PARTITION);		/* u16 tag */
+    stw_raw(w ++, 28);				/* u16 len */
+    strcpy((void *) w, "bootloader");		/* char name[16] */
+    l = (void *) (w + 8);
+    stl_raw(l ++, 0x00020000);			/* unsigned int size */
+    stl_raw(l ++, 0x00000000);			/* unsigned int offset */
+    stl_raw(l ++, 0x3);				/* unsigned int mask_flags */
+    w = (void *) l;
+
+    stw_raw(w ++, OMAP_TAG_PARTITION);		/* u16 tag */
+    stw_raw(w ++, 28);				/* u16 len */
+    strcpy((void *) w, "config");		/* char name[16] */
+    l = (void *) (w + 8);
+    stl_raw(l ++, 0x00060000);			/* unsigned int size */
+    stl_raw(l ++, 0x00020000);			/* unsigned int offset */
+    stl_raw(l ++, 0x0);				/* unsigned int mask_flags */
+    w = (void *) l;
+
+    stw_raw(w ++, OMAP_TAG_PARTITION);		/* u16 tag */
+    stw_raw(w ++, 28);				/* u16 len */
+    strcpy((void *) w, "kernel");		/* char name[16] */
+    l = (void *) (w + 8);
+    stl_raw(l ++, 0x00200000);			/* unsigned int size */
+    stl_raw(l ++, 0x00080000);			/* unsigned int offset */
+    stl_raw(l ++, 0x0);				/* unsigned int mask_flags */
+    w = (void *) l;
+
+    stw_raw(w ++, OMAP_TAG_PARTITION);		/* u16 tag */
+    stw_raw(w ++, 28);				/* u16 len */
+    strcpy((void *) w, "initfs");		/* char name[16] */
+    l = (void *) (w + 8);
+    stl_raw(l ++, 0x00200000);			/* unsigned int size */
+    stl_raw(l ++, 0x00280000);			/* unsigned int offset */
+    stl_raw(l ++, 0x3);				/* unsigned int mask_flags */
+    w = (void *) l;
+
+    stw_raw(w ++, OMAP_TAG_PARTITION);		/* u16 tag */
+    stw_raw(w ++, 28);				/* u16 len */
+    strcpy((void *) w, "rootfs");		/* char name[16] */
+    l = (void *) (w + 8);
+    stl_raw(l ++, 0x0fb80000);			/* unsigned int size */
+    stl_raw(l ++, 0x00480000);			/* unsigned int offset */
+    stl_raw(l ++, 0x3);				/* 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;
+
+    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, "RX-34");		/* 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");			/* char version[12] */
+    w += 6;
+
+    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, "1.1.6-qemu");		/* char version[12] */
+    w += 6;
+
+    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 ++, 5);				/* TODO s16 nreset_gpio */
+    stw_raw(w ++, 16);				/* u8 data_lines */
+
+    return (void *) w - p;
+}
+
+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 void n800_init(int ram_size, int vga_ram_size,
+                const char *boot_device, DisplayState *ds,
+                const char *kernel_filename, const char *kernel_cmdline,
+                const char *initrd_filename, const char *cpu_model)
+{
+    struct n800_s *s = (struct n800_s *) qemu_mallocz(sizeof(*s));
+    int sdram_size = n800_binfo.ram_size;
+    int onenandram_size = 0x00010000;
+
+    if (ram_size < sdram_size + onenandram_size + OMAP242X_SRAM_SIZE) {
+        fprintf(stderr, "This architecture uses %i bytes of memory\n",
+                        sdram_size + onenandram_size + OMAP242X_SRAM_SIZE);
+        exit(1);
+    }
+
+    s->cpu = omap2420_mpu_init(sdram_size, NULL, cpu_model);
+
+    n800_gpio_setup(s);
+    n800_nand_setup(s);
+    n800_i2c_setup(s);
+    n800_tsc_setup(s);
+    n800_spi_setup(s);
+    n800_dss_setup(s, ds);
+    n800_cbus_setup(s);
+
+    /* Setup initial (reset) machine state */
+
+    /* Start at the OneNAND bootloader.  */
+    s->cpu->env->regs[15] = 0;
+
+    if (kernel_filename) {
+        /* Or at the linux loader.  */
+        n800_binfo.kernel_filename = kernel_filename;
+        n800_binfo.kernel_cmdline = kernel_cmdline;
+        n800_binfo.initrd_filename = initrd_filename;
+        arm_load_kernel(s->cpu->env, &n800_binfo);
+
+        qemu_register_reset(n800_boot_init, s);
+        n800_boot_init(s);
+    }
+
+    dpy_resize(ds, 800, 480);
+}
+
+QEMUMachine n800_machine = {
+    "n800",
+    "Nokia N800 aka. RX-34 tablet (OMAP2420)",
+    n800_init,
+};
diff --git a/hw/omap.h b/hw/omap.h
index ecfd54d..de838c9 100644
--- a/hw/omap.h
+++ b/hw/omap.h
@@ -22,6 +22,7 @@
 # define hw_omap_h		"omap.h"
 
 # define OMAP_EMIFS_BASE	0x00000000
+# define OMAP2_Q0_BASE		0x00000000
 # define OMAP_CS0_BASE		0x00000000
 # define OMAP_CS1_BASE		0x04000000
 # define OMAP_CS2_BASE		0x08000000
@@ -29,18 +30,26 @@
 # define OMAP_EMIFF_BASE	0x10000000
 # define OMAP_IMIF_BASE		0x20000000
 # define OMAP_LOCALBUS_BASE	0x30000000
+# define OMAP2_Q1_BASE		0x40000000
+# define OMAP2_L4_BASE		0x48000000
+# define OMAP2_SRAM_BASE	0x40200000
+# define OMAP2_L3_BASE		0x68000000
+# define OMAP2_Q2_BASE		0x80000000
+# define OMAP2_Q3_BASE		0xc0000000
 # define OMAP_MPUI_BASE		0xe1000000
 
 # define OMAP730_SRAM_SIZE	0x00032000
 # define OMAP15XX_SRAM_SIZE	0x00030000
 # define OMAP16XX_SRAM_SIZE	0x00004000
 # define OMAP1611_SRAM_SIZE	0x0003e800
+# define OMAP242X_SRAM_SIZE	0x000a0000
+# define OMAP243X_SRAM_SIZE	0x00010000
 # define OMAP_CS0_SIZE		0x04000000
 # define OMAP_CS1_SIZE		0x04000000
 # define OMAP_CS2_SIZE		0x04000000
 # define OMAP_CS3_SIZE		0x04000000
 
-/* omap1_clk.c */
+/* omap_clk.c */
 struct omap_mpu_state_s;
 typedef struct clk *omap_clk;
 omap_clk omap_findclk(struct omap_mpu_state_s *mpu, const char *name);
@@ -55,14 +64,41 @@ int64_t omap_clk_getrate(omap_clk clk);
 void omap_clk_reparent(omap_clk clk, omap_clk parent);
 
 /* omap[123].c */
+struct omap_l4_s;
+struct omap_l4_s *omap_l4_init(target_phys_addr_t base, int ta_num);
+
+struct omap_target_agent_s;
+struct omap_target_agent_s *omap_l4ta_get(struct omap_l4_s *bus, int cs);
+target_phys_addr_t omap_l4_attach(struct omap_target_agent_s *ta, int region,
+                int iotype);
+
 struct omap_intr_handler_s;
 struct omap_intr_handler_s *omap_inth_init(target_phys_addr_t base,
-                unsigned long size, unsigned char nbanks,
+                unsigned long size, unsigned char nbanks, qemu_irq **pins,
                 qemu_irq parent_irq, qemu_irq parent_fiq, omap_clk clk);
-
-struct omap_target_agent_s;
-static inline target_phys_addr_t omap_l4_attach(struct omap_target_agent_s *ta,
-                int region, int iotype) { return 0; }
+struct omap_intr_handler_s *omap2_inth_init(target_phys_addr_t base,
+                int size, int nbanks, qemu_irq **pins,
+                qemu_irq parent_irq, qemu_irq parent_fiq,
+                omap_clk fclk, omap_clk iclk);
+void omap_inth_reset(struct omap_intr_handler_s *s);
+
+struct omap_prcm_s;
+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_sysctl_s;
+struct omap_sysctl_s *omap_sysctl_init(struct omap_target_agent_s *ta,
+                omap_clk iclk, struct omap_mpu_state_s *mpu);
+
+struct omap_sdrc_s;
+struct omap_sdrc_s *omap_sdrc_init(target_phys_addr_t base);
+
+struct omap_gpmc_s;
+struct omap_gpmc_s *omap_gpmc_init(target_phys_addr_t base, qemu_irq irq);
+void omap_gpmc_attach(struct omap_gpmc_s *s, int cs, int iomemtype,
+                void (*base_upd)(void *opaque, target_phys_addr_t new),
+                void (*unmap)(void *opaque), void *opaque);
 
 /*
  * Common IRQ numbers for level 1 interrupt handler
@@ -295,10 +331,20 @@ static inline target_phys_addr_t omap_l4_attach(struct omap_target_agent_s *ta,
  * OMAP-24xx common IRQ numbers
  */
 # define OMAP_INT_24XX_SYS_NIRQ		7
+# define OMAP_INT_24XX_L3_IRQ		10
+# define OMAP_INT_24XX_PRCM_MPU_IRQ	11
 # define OMAP_INT_24XX_SDMA_IRQ0	12
 # define OMAP_INT_24XX_SDMA_IRQ1	13
 # define OMAP_INT_24XX_SDMA_IRQ2	14
 # define OMAP_INT_24XX_SDMA_IRQ3	15
+# define OMAP_INT_243X_MCBSP2_IRQ	16
+# define OMAP_INT_243X_MCBSP3_IRQ	17
+# define OMAP_INT_243X_MCBSP4_IRQ	18
+# define OMAP_INT_243X_MCBSP5_IRQ	19
+# define OMAP_INT_24XX_GPMC_IRQ		20
+# define OMAP_INT_24XX_GUFFAW_IRQ	21
+# define OMAP_INT_24XX_IVA_IRQ		22
+# define OMAP_INT_24XX_EAC_IRQ		23
 # define OMAP_INT_24XX_CAM_IRQ		24
 # define OMAP_INT_24XX_DSS_IRQ		25
 # define OMAP_INT_24XX_MAIL_U0_MPU	26
@@ -308,8 +354,10 @@ static inline target_phys_addr_t omap_l4_attach(struct omap_target_agent_s *ta,
 # define OMAP_INT_24XX_GPIO_BANK2	30
 # define OMAP_INT_24XX_GPIO_BANK3	31
 # define OMAP_INT_24XX_GPIO_BANK4	32
-# define OMAP_INT_24XX_GPIO_BANK5	33
+# define OMAP_INT_243X_GPIO_BANK5	33
 # define OMAP_INT_24XX_MAIL_U3_MPU	34
+# define OMAP_INT_24XX_WDT3		35
+# define OMAP_INT_24XX_WDT4		36
 # define OMAP_INT_24XX_GPTIMER1		37
 # define OMAP_INT_24XX_GPTIMER2		38
 # define OMAP_INT_24XX_GPTIMER3		39
@@ -322,10 +370,24 @@ static inline target_phys_addr_t omap_l4_attach(struct omap_target_agent_s *ta,
 # define OMAP_INT_24XX_GPTIMER10	46
 # define OMAP_INT_24XX_GPTIMER11	47
 # define OMAP_INT_24XX_GPTIMER12	48
+# define OMAP_INT_24XX_PKA_IRQ		50
+# define OMAP_INT_24XX_SHA1MD5_IRQ	51
+# define OMAP_INT_24XX_RNG_IRQ		52
+# define OMAP_INT_24XX_MG_IRQ		53
+# define OMAP_INT_24XX_I2C1_IRQ		56
+# define OMAP_INT_24XX_I2C2_IRQ		57
 # define OMAP_INT_24XX_MCBSP1_IRQ_TX	59
 # define OMAP_INT_24XX_MCBSP1_IRQ_RX	60
 # define OMAP_INT_24XX_MCBSP2_IRQ_TX	62
 # define OMAP_INT_24XX_MCBSP2_IRQ_RX	63
+# define OMAP_INT_243X_MCBSP1_IRQ	64
+# define OMAP_INT_24XX_MCSPI1_IRQ	65
+# define OMAP_INT_24XX_MCSPI2_IRQ	66
+# define OMAP_INT_24XX_SSI1_IRQ0	67
+# define OMAP_INT_24XX_SSI1_IRQ1	68
+# define OMAP_INT_24XX_SSI2_IRQ0	69
+# define OMAP_INT_24XX_SSI2_IRQ1	70
+# define OMAP_INT_24XX_SSI_GDD_IRQ	71
 # define OMAP_INT_24XX_UART1_IRQ	72
 # define OMAP_INT_24XX_UART2_IRQ	73
 # define OMAP_INT_24XX_UART3_IRQ	74
@@ -335,10 +397,15 @@ static inline target_phys_addr_t omap_l4_attach(struct omap_target_agent_s *ta,
 # define OMAP_INT_24XX_USB_IRQ_HGEN	78
 # define OMAP_INT_24XX_USB_IRQ_HSOF	79
 # define OMAP_INT_24XX_USB_IRQ_OTG	80
+# define OMAP_INT_24XX_VLYNQ_IRQ	81
 # define OMAP_INT_24XX_MMC_IRQ		83
+# define OMAP_INT_24XX_MS_IRQ		84
+# define OMAP_INT_24XX_FAC_IRQ		85
+# define OMAP_INT_24XX_MCSPI3_IRQ	91
 # define OMAP_INT_243X_HS_USB_MC	92
 # define OMAP_INT_243X_HS_USB_DMA	93
 # define OMAP_INT_243X_CARKIT		94
+# define OMAP_INT_34XX_GPTIMER12	95
 
 /* omap_dma.c */
 enum omap_dma_model {
@@ -352,6 +419,9 @@ struct omap_dma_s;
 struct omap_dma_s *omap_dma_init(target_phys_addr_t base, qemu_irq *irqs,
                 qemu_irq lcd_irq, struct omap_mpu_state_s *mpu, omap_clk clk,
                 enum omap_dma_model model);
+struct omap_dma_s *omap_dma4_init(target_phys_addr_t base, qemu_irq *irqs,
+                struct omap_mpu_state_s *mpu, int fifo,
+                int chans, omap_clk iclk, omap_clk fclk);
 void omap_dma_reset(struct omap_dma_s *s);
 
 struct dma_irq_map {
@@ -367,7 +437,7 @@ enum omap_dma_port {
     tipb,
     local,	/* omap16xx: ocp_t2 */
     tipb_mpui,
-    omap_dma_port_last,
+    __omap_dma_port_last,
 };
 
 typedef enum {
@@ -488,11 +558,83 @@ struct omap_dma_lcd_channel_s {
 # define OMAP_DMA_MMC2_RX		55
 # define OMAP_DMA_CRYPTO_DES_OUT	56
 
+/*
+ * DMA request numbers for the OMAP2
+ */
+# define OMAP24XX_DMA_NO_DEVICE		0
+# define OMAP24XX_DMA_XTI_DMA		1	/* Not in OMAP2420 */
+# define OMAP24XX_DMA_EXT_DMAREQ0	2
+# define OMAP24XX_DMA_EXT_DMAREQ1	3
+# define OMAP24XX_DMA_GPMC		4
+# define OMAP24XX_DMA_GFX		5	/* Not in OMAP2420 */
+# define OMAP24XX_DMA_DSS		6
+# define OMAP24XX_DMA_VLYNQ_TX		7	/* Not in OMAP2420 */
+# define OMAP24XX_DMA_CWT		8	/* Not in OMAP2420 */
+# define OMAP24XX_DMA_AES_TX		9	/* Not in OMAP2420 */
+# define OMAP24XX_DMA_AES_RX		10	/* Not in OMAP2420 */
+# define OMAP24XX_DMA_DES_TX		11	/* Not in OMAP2420 */
+# define OMAP24XX_DMA_DES_RX		12	/* Not in OMAP2420 */
+# define OMAP24XX_DMA_SHA1MD5_RX	13	/* Not in OMAP2420 */
+# define OMAP24XX_DMA_EXT_DMAREQ2	14
+# define OMAP24XX_DMA_EXT_DMAREQ3	15
+# define OMAP24XX_DMA_EXT_DMAREQ4	16
+# define OMAP24XX_DMA_EAC_AC_RD		17
+# define OMAP24XX_DMA_EAC_AC_WR		18
+# define OMAP24XX_DMA_EAC_MD_UL_RD	19
+# define OMAP24XX_DMA_EAC_MD_UL_WR	20
+# define OMAP24XX_DMA_EAC_MD_DL_RD	21
+# define OMAP24XX_DMA_EAC_MD_DL_WR	22
+# define OMAP24XX_DMA_EAC_BT_UL_RD	23
+# define OMAP24XX_DMA_EAC_BT_UL_WR	24
+# define OMAP24XX_DMA_EAC_BT_DL_RD	25
+# define OMAP24XX_DMA_EAC_BT_DL_WR	26
+# define OMAP24XX_DMA_I2C1_TX		27
+# define OMAP24XX_DMA_I2C1_RX		28
+# define OMAP24XX_DMA_I2C2_TX		29
+# define OMAP24XX_DMA_I2C2_RX		30
+# define OMAP24XX_DMA_MCBSP1_TX		31
+# define OMAP24XX_DMA_MCBSP1_RX		32
+# define OMAP24XX_DMA_MCBSP2_TX		33
+# define OMAP24XX_DMA_MCBSP2_RX		34
+# define OMAP24XX_DMA_SPI1_TX0		35
+# define OMAP24XX_DMA_SPI1_RX0		36
+# define OMAP24XX_DMA_SPI1_TX1		37
+# define OMAP24XX_DMA_SPI1_RX1		38
+# define OMAP24XX_DMA_SPI1_TX2		39
+# define OMAP24XX_DMA_SPI1_RX2		40
+# define OMAP24XX_DMA_SPI1_TX3		41
+# define OMAP24XX_DMA_SPI1_RX3		42
+# define OMAP24XX_DMA_SPI2_TX0		43
+# define OMAP24XX_DMA_SPI2_RX0		44
+# define OMAP24XX_DMA_SPI2_TX1		45
+# define OMAP24XX_DMA_SPI2_RX1		46
+
+# define OMAP24XX_DMA_UART1_TX		49
+# define OMAP24XX_DMA_UART1_RX		50
+# define OMAP24XX_DMA_UART2_TX		51
+# define OMAP24XX_DMA_UART2_RX		52
+# define OMAP24XX_DMA_UART3_TX		53
+# define OMAP24XX_DMA_UART3_RX		54
+# define OMAP24XX_DMA_USB_W2FC_TX0	55
+# define OMAP24XX_DMA_USB_W2FC_RX0	56
+# define OMAP24XX_DMA_USB_W2FC_TX1	57
+# define OMAP24XX_DMA_USB_W2FC_RX1	58
+# define OMAP24XX_DMA_USB_W2FC_TX2	59
+# define OMAP24XX_DMA_USB_W2FC_RX2	60
+# define OMAP24XX_DMA_MMC1_TX		61
+# define OMAP24XX_DMA_MMC1_RX		62
+# define OMAP24XX_DMA_MS		63	/* Not in OMAP2420 */
+# define OMAP24XX_DMA_EXT_DMAREQ5	64
+
 /* omap[123].c */
 struct omap_mpu_timer_s;
 struct omap_mpu_timer_s *omap_mpu_timer_init(target_phys_addr_t base,
                 qemu_irq irq, omap_clk clk);
 
+struct omap_gp_timer_s;
+struct omap_gp_timer_s *omap_gp_timer_init(struct omap_target_agent_s *ta,
+                qemu_irq irq, omap_clk fclk, omap_clk iclk);
+
 struct omap_watchdog_timer_s;
 struct omap_watchdog_timer_s *omap_wd_timer_init(target_phys_addr_t base,
                 qemu_irq irq, omap_clk clk);
@@ -501,13 +643,21 @@ struct omap_32khz_timer_s;
 struct omap_32khz_timer_s *omap_os_timer_init(target_phys_addr_t base,
                 qemu_irq irq, omap_clk clk);
 
+void omap_synctimer_init(struct omap_target_agent_s *ta,
+                struct omap_mpu_state_s *mpu, omap_clk fclk, omap_clk iclk);
+
 struct omap_tipb_bridge_s;
 struct omap_tipb_bridge_s *omap_tipb_bridge_init(target_phys_addr_t base,
                 qemu_irq abort_irq, omap_clk clk);
 
 struct omap_uart_s;
 struct omap_uart_s *omap_uart_init(target_phys_addr_t base,
-                qemu_irq irq, omap_clk clk, CharDriverState *chr);
+                qemu_irq irq, omap_clk fclk, omap_clk iclk,
+                qemu_irq txdma, qemu_irq rxdma, CharDriverState *chr);
+struct omap_uart_s *omap2_uart_init(struct omap_target_agent_s *ta,
+                qemu_irq irq, omap_clk fclk, omap_clk iclk,
+                qemu_irq txdma, qemu_irq rxdma, CharDriverState *chr);
+void omap_uart_reset(struct omap_uart_s *s);
 
 struct omap_mpuio_s;
 struct omap_mpuio_s *omap_mpuio_init(target_phys_addr_t base,
@@ -523,6 +673,12 @@ struct omap_gpio_s *omap_gpio_init(target_phys_addr_t base,
 qemu_irq *omap_gpio_in_get(struct omap_gpio_s *s);
 void omap_gpio_out_set(struct omap_gpio_s *s, int line, qemu_irq handler);
 
+struct omap_gpif_s;
+struct omap_gpif_s *omap2_gpio_init(struct omap_target_agent_s *ta,
+                qemu_irq *irq, omap_clk *fclk, omap_clk iclk, int modules);
+qemu_irq *omap2_gpio_in_get(struct omap_gpif_s *s, int start);
+void omap2_gpio_out_set(struct omap_gpif_s *s, int line, qemu_irq handler);
+
 struct uwire_slave_s {
     uint16_t (*receive)(void *opaque);
     void (*send)(void *opaque, uint16_t data);
@@ -534,6 +690,13 @@ struct omap_uwire_s *omap_uwire_init(target_phys_addr_t base,
 void omap_uwire_attach(struct omap_uwire_s *s,
                 struct uwire_slave_s *slave, int chipselect);
 
+struct omap_mcspi_s;
+struct omap_mcspi_s *omap_mcspi_init(struct omap_target_agent_s *ta, int chnum,
+                qemu_irq irq, qemu_irq *drq, omap_clk fclk, omap_clk iclk);
+void omap_mcspi_attach(struct omap_mcspi_s *s,
+                uint32_t (*txrx)(void *opaque, uint32_t), void *opaque,
+                int chipselect);
+
 struct omap_rtc_s;
 struct omap_rtc_s *omap_rtc_init(target_phys_addr_t base,
                 qemu_irq *irq, omap_clk clk);
@@ -570,6 +733,9 @@ void omap_mcbsp_i2s_attach(struct omap_mcbsp_s *s, struct i2s_codec_s *slave);
 struct omap_lpg_s;
 struct omap_lpg_s *omap_lpg_init(target_phys_addr_t base, omap_clk clk);
 
+void omap_tap_init(struct omap_target_agent_s *ta,
+                struct omap_mpu_state_s *mpu);
+
 /* omap_lcdc.c */
 struct omap_lcd_panel_s;
 void omap_lcdc_reset(struct omap_lcd_panel_s *s);
@@ -577,13 +743,33 @@ struct omap_lcd_panel_s *omap_lcdc_init(target_phys_addr_t base, qemu_irq irq,
                 struct omap_dma_lcd_channel_s *dma, DisplayState *ds,
                 ram_addr_t imif_base, ram_addr_t emiff_base, omap_clk clk);
 
+/* omap_dss.c */
+struct rfbi_chip_s {
+    void *opaque;
+    void (*write)(void *opaque, int dc, uint16_t value);
+    void (*block)(void *opaque, int dc, void *buf, size_t len, int pitch);
+    uint16_t (*read)(void *opaque, int dc);
+};
+struct omap_dss_s;
+void omap_dss_reset(struct omap_dss_s *s);
+struct omap_dss_s *omap_dss_init(struct omap_target_agent_s *ta,
+                target_phys_addr_t l3_base, DisplayState *ds,
+                qemu_irq irq, qemu_irq drq,
+                omap_clk fck1, omap_clk fck2, omap_clk ck54m,
+                omap_clk ick1, omap_clk ick2);
+void omap_rfbi_attach(struct omap_dss_s *s, int cs, struct rfbi_chip_s *chip);
+
 /* omap_mmc.c */
 struct omap_mmc_s;
 struct omap_mmc_s *omap_mmc_init(target_phys_addr_t base,
                 BlockDriverState *bd,
                 qemu_irq irq, qemu_irq dma[], omap_clk clk);
+struct omap_mmc_s *omap2_mmc_init(struct omap_target_agent_s *ta,
+                BlockDriverState *bd, qemu_irq irq, qemu_irq dma[],
+                omap_clk fclk, omap_clk iclk);
 void omap_mmc_reset(struct omap_mmc_s *s);
 void omap_mmc_handlers(struct omap_mmc_s *s, qemu_irq ro, qemu_irq cover);
+void omap_mmc_enable(struct omap_mmc_s *s, int enable);
 
 /* omap_i2c.c */
 struct omap_i2c_s;
@@ -596,14 +782,37 @@ i2c_bus *omap_i2c_bus(struct omap_i2c_s *s);
 
 # define cpu_is_omap310(cpu)		(cpu->mpu_model == omap310)
 # define cpu_is_omap1510(cpu)		(cpu->mpu_model == omap1510)
+# define cpu_is_omap1610(cpu)		(cpu->mpu_model == omap1610)
+# define cpu_is_omap1710(cpu)		(cpu->mpu_model == omap1710)
+# define cpu_is_omap2410(cpu)		(cpu->mpu_model == omap2410)
+# define cpu_is_omap2420(cpu)		(cpu->mpu_model == omap2420)
+# define cpu_is_omap2430(cpu)		(cpu->mpu_model == omap2430)
+# define cpu_is_omap3430(cpu)		(cpu->mpu_model == omap3430)
+
 # define cpu_is_omap15xx(cpu)		\
         (cpu_is_omap310(cpu) || cpu_is_omap1510(cpu))
-# define cpu_class_omap1(cpu)		1
+# define cpu_is_omap16xx(cpu)		\
+        (cpu_is_omap1610(cpu) || cpu_is_omap1710(cpu))
+# define cpu_is_omap24xx(cpu)		\
+        (cpu_is_omap2410(cpu) || cpu_is_omap2420(cpu) || cpu_is_omap2430(cpu))
+
+# define cpu_class_omap1(cpu)		\
+        (cpu_is_omap15xx(cpu) || cpu_is_omap16xx(cpu))
+# define cpu_class_omap2(cpu)		cpu_is_omap24xx(cpu)
+# define cpu_class_omap3(cpu)		cpu_is_omap3430(cpu)
 
 struct omap_mpu_state_s {
-    enum omap1_mpu_model {
+    enum omap_mpu_model {
         omap310,
         omap1510,
+        omap1610,
+        omap1710,
+        omap2410,
+        omap2420,
+        omap2422,
+        omap2423,
+        omap2430,
+        omap3430,
     } mpu_model;
 
     CPUState *env;
@@ -620,7 +829,7 @@ struct omap_mpu_state_s {
                         target_phys_addr_t offset, uint32_t value);
         int (*addr_valid)(struct omap_mpu_state_s *s,
                         target_phys_addr_t addr);
-    } port[omap_dma_port_last];
+    } port[__omap_dma_port_last];
 
     unsigned long sdram_size;
     unsigned long sram_size;
@@ -656,7 +865,7 @@ struct omap_mpu_state_s {
         omap_clk clk;
     } pwt;
 
-    struct omap_i2c_s *i2c;
+    struct omap_i2c_s *i2c[2];
 
     struct omap_rtc_s *rtc;
 
@@ -722,7 +931,38 @@ struct omap_mpu_state_s {
         uint16_t dsp_idlect2;
         uint16_t dsp_rstct2;
     } clkm;
-} *omap310_mpu_init(unsigned long sdram_size,
+
+    /* OMAP2-only peripherals */
+    struct omap_l4_s *l4;
+
+    struct omap_gp_timer_s *gptimer[12];
+
+    target_phys_addr_t tap_base;
+
+    struct omap_synctimer_s {
+        target_phys_addr_t base;
+        uint32_t val;
+        uint16_t readh;
+    } synctimer;
+
+    struct omap_prcm_s *prcm;
+    struct omap_sdrc_s *sdrc;
+    struct omap_gpmc_s *gpmc;
+    struct omap_sysctl_s *sysc;
+
+    struct omap_gpif_s *gpif;
+
+    struct omap_mcspi_s *mcspi[2];
+
+    struct omap_dss_s *dss;
+};
+
+/* omap1.c */
+struct omap_mpu_state_s *omap310_mpu_init(unsigned long sdram_size,
+                DisplayState *ds, const char *core);
+
+/* omap2.c */
+struct omap_mpu_state_s *omap2420_mpu_init(unsigned long sdram_size,
                 DisplayState *ds, const char *core);
 
 # if TARGET_PHYS_ADDR_BITS == 32
@@ -743,24 +983,46 @@ uint32_t omap_badwidth_read32(void *opaque, target_phys_addr_t addr);
 void omap_badwidth_write32(void *opaque, target_phys_addr_t addr,
                 uint32_t value);
 
+void omap_mpu_wakeup(void *opaque, int irq, int req);
+
 # define OMAP_BAD_REG(paddr)		\
-        printf("%s: Bad register " OMAP_FMT_plx "\n", __FUNCTION__, paddr)
+        fprintf(stderr, "%s: Bad register " OMAP_FMT_plx "\n",	\
+                        __FUNCTION__, paddr)
 # define OMAP_RO_REG(paddr)		\
-        printf("%s: Read-only register " OMAP_FMT_plx "\n",	\
+        fprintf(stderr, "%s: Read-only register " OMAP_FMT_plx "\n",	\
                         __FUNCTION__, paddr)
 
+/* OMAP-specific Linux bootloader tags for the ATAG_BOARD area
+   (Board-specifc tags are not here)  */
+#define OMAP_TAG_CLOCK		0x4f01
+#define OMAP_TAG_MMC		0x4f02
+#define OMAP_TAG_SERIAL_CONSOLE	0x4f03
+#define OMAP_TAG_USB		0x4f04
+#define OMAP_TAG_LCD		0x4f05
+#define OMAP_TAG_GPIO_SWITCH	0x4f06
+#define OMAP_TAG_UART		0x4f07
+#define OMAP_TAG_FBMEM		0x4f08
+#define OMAP_TAG_STI_CONSOLE	0x4f09
+#define OMAP_TAG_CAMERA_SENSOR	0x4f0a
+#define OMAP_TAG_PARTITION	0x4f0b
+#define OMAP_TAG_TEA5761	0x4f10
+#define OMAP_TAG_TMP105		0x4f11
+#define OMAP_TAG_BOOT_REASON	0x4f80
+#define OMAP_TAG_FLASH_PART_STR	0x4f81
+#define OMAP_TAG_VERSION_STR	0x4f82
+
 # define TCMI_VERBOSE			1
 //# define MEM_VERBOSE			1
 
 # ifdef TCMI_VERBOSE
 #  define OMAP_8B_REG(paddr)		\
-        printf("%s: 8-bit register " OMAP_FMT_plx "\n",	\
+        fprintf(stderr, "%s: 8-bit register " OMAP_FMT_plx "\n",	\
                         __FUNCTION__, paddr)
 #  define OMAP_16B_REG(paddr)		\
-        printf("%s: 16-bit register " OMAP_FMT_plx "\n",	\
+        fprintf(stderr, "%s: 16-bit register " OMAP_FMT_plx "\n",	\
                         __FUNCTION__, paddr)
 #  define OMAP_32B_REG(paddr)		\
-        printf("%s: 32-bit register " OMAP_FMT_plx "\n",	\
+        fprintf(stderr, "%s: 32-bit register " OMAP_FMT_plx "\n",	\
                         __FUNCTION__, paddr)
 # else
 #  define OMAP_8B_REG(paddr)
@@ -863,10 +1125,4 @@ inline static int debug_register_io_memory(int io_index,
 #  define cpu_register_io_memory	debug_register_io_memory
 # endif
 
-/* Not really omap specific, but is the only thing that uses the
-   uwire interface.  */
-/* tsc210x.c */
-struct uwire_slave_s *tsc2102_init(qemu_irq pint, AudioState *audio);
-struct i2s_codec_s *tsc210x_codec(struct uwire_slave_s *chip);
-
 #endif /* hw_omap_h */
diff --git a/hw/omap1.c b/hw/omap1.c
index 3888e80..d81cbce 100644
--- a/hw/omap1.c
+++ b/hw/omap1.c
@@ -23,10 +23,11 @@
 #include "omap.h"
 #include "sysemu.h"
 #include "qemu-timer.h"
+#include "qemu-char.h"
 /* We use pc-style serial ports.  */
 #include "pc.h"
 
-/* Should signal the TCMI */
+/* Should signal the TCMI/GPMC */
 uint32_t omap_badwidth_read8(void *opaque, target_phys_addr_t addr)
 {
     uint8_t ret;
@@ -86,6 +87,7 @@ struct omap_intr_handler_bank_s {
     uint32_t mask;
     uint32_t fiq;
     uint32_t sens_edge;
+    uint32_t swi;
     unsigned char priority[32];
 };
 
@@ -94,11 +96,14 @@ struct omap_intr_handler_s {
     qemu_irq parent_intr[2];
     target_phys_addr_t base;
     unsigned char nbanks;
+    int level_only;
 
     /* state */
     uint32_t new_agr[2];
     int sir_intr[2];
-    struct omap_intr_handler_bank_s banks[];
+    int autoidle;
+    uint32_t mask;
+    struct omap_intr_handler_bank_s bank[];
 };
 
 static void omap_inth_sir_update(struct omap_intr_handler_s *s, int is_fiq)
@@ -113,11 +118,11 @@ static void omap_inth_sir_update(struct omap_intr_handler_s *s, int is_fiq)
      * If all interrupts have the same priority, the default order is IRQ_N,
      * IRQ_N-1,...,IRQ_0. */
     for (j = 0; j < s->nbanks; ++j) {
-        level = s->banks[j].irqs & ~s->banks[j].mask &
-                (is_fiq ? s->banks[j].fiq : ~s->banks[j].fiq);
+        level = s->bank[j].irqs & ~s->bank[j].mask &
+                (is_fiq ? s->bank[j].fiq : ~s->bank[j].fiq);
         for (f = ffs(level), i = f - 1, level >>= f - 1; f; i += f,
                         level >>= f) {
-            p = s->banks[j].priority[i];
+            p = s->bank[j].priority[i];
             if (p <= p_intr) {
                 p_intr = p;
                 sir_intr = 32 * j + i;
@@ -134,10 +139,10 @@ static inline void omap_inth_update(struct omap_intr_handler_s *s, int is_fiq)
     uint32_t has_intr = 0;
 
     for (i = 0; i < s->nbanks; ++i)
-        has_intr |= s->banks[i].irqs & ~s->banks[i].mask &
-                (is_fiq ? s->banks[i].fiq : ~s->banks[i].fiq);
+        has_intr |= s->bank[i].irqs & ~s->bank[i].mask &
+                (is_fiq ? s->bank[i].fiq : ~s->bank[i].fiq);
 
-    if (s->new_agr[is_fiq] && has_intr) {
+    if (s->new_agr[is_fiq] & has_intr & s->mask) {
         s->new_agr[is_fiq] = 0;
         omap_inth_sir_update(s, is_fiq);
         qemu_set_irq(s->parent_intr[is_fiq], 1);
@@ -152,13 +157,13 @@ static void omap_set_intr(void *opaque, int irq, int req)
     struct omap_intr_handler_s *ih = (struct omap_intr_handler_s *) opaque;
     uint32_t rise;
 
-    struct omap_intr_handler_bank_s *bank = &ih->banks[irq >> 5];
+    struct omap_intr_handler_bank_s *bank = &ih->bank[irq >> 5];
     int n = irq & 31;
 
     if (req) {
         rise = ~bank->irqs & (1 << n);
         if (~bank->sens_edge & (1 << n))
-            rise &= ~bank->inputs & (1 << n);
+            rise &= ~bank->inputs;
 
         bank->inputs |= (1 << n);
         if (rise) {
@@ -173,13 +178,33 @@ static void omap_set_intr(void *opaque, int irq, int req)
     }
 }
 
+/* Simplified version with no edge detection */
+static void omap_set_intr_noedge(void *opaque, int irq, int req)
+{
+    struct omap_intr_handler_s *ih = (struct omap_intr_handler_s *) opaque;
+    uint32_t rise;
+
+    struct omap_intr_handler_bank_s *bank = &ih->bank[irq >> 5];
+    int n = irq & 31;
+
+    if (req) {
+        rise = ~bank->inputs & (1 << n);
+        if (rise) {
+            bank->irqs |= bank->inputs |= rise;
+            omap_inth_update(ih, 0);
+            omap_inth_update(ih, 1);
+        }
+    } else
+        bank->irqs = (bank->inputs &= ~(1 << n)) | bank->swi;
+}
+
 static uint32_t omap_inth_read(void *opaque, target_phys_addr_t addr)
 {
     struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque;
     int i, offset = addr - s->base;
     int bank_no = offset >> 8;
     int line_no;
-    struct omap_intr_handler_bank_s *bank = &s->banks[bank_no];
+    struct omap_intr_handler_bank_s *bank = &s->bank[bank_no];
     offset &= 0xff;
 
     switch (offset) {
@@ -194,7 +219,7 @@ static uint32_t omap_inth_read(void *opaque, target_phys_addr_t addr)
         if (bank_no != 0)
             break;
         line_no = s->sir_intr[(offset - 0x10) >> 2];
-        bank = &s->banks[line_no >> 5];
+        bank = &s->bank[line_no >> 5];
         i = line_no & 31;
         if (((bank->sens_edge >> i) & 1) == INT_FALLING_EDGE)
             bank->irqs &= ~(1 << i);
@@ -256,7 +281,7 @@ static void omap_inth_write(void *opaque, target_phys_addr_t addr,
     struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque;
     int i, offset = addr - s->base;
     int bank_no = offset >> 8;
-    struct omap_intr_handler_bank_s *bank = &s->banks[bank_no];
+    struct omap_intr_handler_bank_s *bank = &s->bank[bank_no];
     offset &= 0xff;
 
     switch (offset) {
@@ -360,25 +385,31 @@ void omap_inth_reset(struct omap_intr_handler_s *s)
     int i;
 
     for (i = 0; i < s->nbanks; ++i){
-        s->banks[i].irqs = 0x00000000;
-        s->banks[i].mask = 0xffffffff;
-        s->banks[i].sens_edge = 0x00000000;
-        s->banks[i].fiq = 0x00000000;
-        s->banks[i].inputs = 0x00000000;
-        memset(s->banks[i].priority, 0, sizeof(s->banks[i].priority));
+        s->bank[i].irqs = 0x00000000;
+        s->bank[i].mask = 0xffffffff;
+        s->bank[i].sens_edge = 0x00000000;
+        s->bank[i].fiq = 0x00000000;
+        s->bank[i].inputs = 0x00000000;
+        s->bank[i].swi = 0x00000000;
+        memset(s->bank[i].priority, 0, sizeof(s->bank[i].priority));
+
+        if (s->level_only)
+            s->bank[i].sens_edge = 0xffffffff;
     }
 
     s->new_agr[0] = ~0;
     s->new_agr[1] = ~0;
     s->sir_intr[0] = 0;
     s->sir_intr[1] = 0;
+    s->autoidle = 0;
+    s->mask = ~0;
 
     qemu_set_irq(s->parent_intr[0], 0);
     qemu_set_irq(s->parent_intr[1], 0);
 }
 
 struct omap_intr_handler_s *omap_inth_init(target_phys_addr_t base,
-                unsigned long size, unsigned char nbanks,
+                unsigned long size, unsigned char nbanks, qemu_irq **pins,
                 qemu_irq parent_irq, qemu_irq parent_fiq, omap_clk clk)
 {
     int iomemtype;
@@ -391,6 +422,8 @@ struct omap_intr_handler_s *omap_inth_init(target_phys_addr_t base,
     s->base = base;
     s->nbanks = nbanks;
     s->pins = qemu_allocate_irqs(omap_set_intr, s, nbanks * 32);
+    if (pins)
+        *pins = s->pins;
 
     omap_inth_reset(s);
 
@@ -401,6 +434,227 @@ struct omap_intr_handler_s *omap_inth_init(target_phys_addr_t base,
     return s;
 }
 
+static uint32_t omap2_inth_read(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque;
+    int offset = addr - s->base;
+    int bank_no, line_no;
+    struct omap_intr_handler_bank_s *bank = 0;
+
+    if ((offset & 0xf80) == 0x80) {
+        bank_no = (offset & 0x60) >> 5;
+        if (bank_no < s->nbanks) {
+            offset &= ~0x60;
+            bank = &s->bank[bank_no];
+        }
+    }
+
+    switch (offset) {
+    case 0x00:	/* INTC_REVISION */
+        return 0x21;
+
+    case 0x10:	/* INTC_SYSCONFIG */
+        return (s->autoidle >> 2) & 1;
+
+    case 0x14:	/* INTC_SYSSTATUS */
+        return 1;						/* RESETDONE */
+
+    case 0x40:	/* INTC_SIR_IRQ */
+        return s->sir_intr[0];
+
+    case 0x44:	/* INTC_SIR_FIQ */
+        return s->sir_intr[1];
+
+    case 0x48:	/* INTC_CONTROL */
+        return (!s->mask) << 2;					/* GLOBALMASK */
+
+    case 0x4c:	/* INTC_PROTECTION */
+        return 0;
+
+    case 0x50:	/* INTC_IDLE */
+        return s->autoidle & 3;
+
+    /* Per-bank registers */
+    case 0x80:	/* INTC_ITR */
+        return bank->inputs;
+
+    case 0x84:	/* INTC_MIR */
+        return bank->mask;
+
+    case 0x88:	/* INTC_MIR_CLEAR */
+    case 0x8c:	/* INTC_MIR_SET */
+        return 0;
+
+    case 0x90:	/* INTC_ISR_SET */
+        return bank->swi;
+
+    case 0x94:	/* INTC_ISR_CLEAR */
+        return 0;
+
+    case 0x98:	/* INTC_PENDING_IRQ */
+        return bank->irqs & ~bank->mask & ~bank->fiq;
+
+    case 0x9c:	/* INTC_PENDING_FIQ */
+        return bank->irqs & ~bank->mask & bank->fiq;
+
+    /* Per-line registers */
+    case 0x100 ... 0x300:	/* INTC_ILR */
+        bank_no = (offset - 0x100) >> 7;
+        if (bank_no > s->nbanks)
+            break;
+        bank = &s->bank[bank_no];
+        line_no = (offset & 0x7f) >> 2;
+        return (bank->priority[line_no] << 2) |
+                ((bank->fiq >> line_no) & 1);
+    }
+    OMAP_BAD_REG(addr);
+    return 0;
+}
+
+static void omap2_inth_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque;
+    int offset = addr - s->base;
+    int bank_no, line_no;
+    struct omap_intr_handler_bank_s *bank = 0;
+
+    if ((offset & 0xf80) == 0x80) {
+        bank_no = (offset & 0x60) >> 5;
+        if (bank_no < s->nbanks) {
+            offset &= ~0x60;
+            bank = &s->bank[bank_no];
+        }
+    }
+
+    switch (offset) {
+    case 0x10:	/* INTC_SYSCONFIG */
+        s->autoidle &= 4;
+        s->autoidle |= (value & 1) << 2;
+        if (value & 2)						/* SOFTRESET */
+            omap_inth_reset(s);
+        return;
+
+    case 0x48:	/* INTC_CONTROL */
+        s->mask = (value & 4) ? 0 : ~0;				/* GLOBALMASK */
+        if (value & 2) {					/* NEWFIQAGR */
+            qemu_set_irq(s->parent_intr[1], 0);
+            s->new_agr[1] = ~0;
+            omap_inth_update(s, 1);
+        }
+        if (value & 1) {					/* NEWIRQAGR */
+            qemu_set_irq(s->parent_intr[0], 0);
+            s->new_agr[0] = ~0;
+            omap_inth_update(s, 0);
+        }
+        return;
+
+    case 0x4c:	/* INTC_PROTECTION */
+        /* TODO: Make a bitmap (or sizeof(char)map) of access privileges
+         * for every register, see Chapter 3 and 4 for privileged mode.  */
+        if (value & 1)
+            fprintf(stderr, "%s: protection mode enable attempt\n",
+                            __FUNCTION__);
+        return;
+
+    case 0x50:	/* INTC_IDLE */
+        s->autoidle &= ~3;
+        s->autoidle |= value & 3;
+        return;
+
+    /* Per-bank registers */
+    case 0x84:	/* INTC_MIR */
+        bank->mask = value;
+        omap_inth_update(s, 0);
+        omap_inth_update(s, 1);
+        return;
+
+    case 0x88:	/* INTC_MIR_CLEAR */
+        bank->mask &= ~value;
+        omap_inth_update(s, 0);
+        omap_inth_update(s, 1);
+        return;
+
+    case 0x8c:	/* INTC_MIR_SET */
+        bank->mask |= value;
+        return;
+
+    case 0x90:	/* INTC_ISR_SET */
+        bank->irqs |= bank->swi |= value;
+        omap_inth_update(s, 0);
+        omap_inth_update(s, 1);
+        return;
+
+    case 0x94:	/* INTC_ISR_CLEAR */
+        bank->swi &= ~value;
+        bank->irqs = bank->swi & bank->inputs;
+        return;
+
+    /* Per-line registers */
+    case 0x100 ... 0x300:	/* INTC_ILR */
+        bank_no = (offset - 0x100) >> 7;
+        if (bank_no > s->nbanks)
+            break;
+        bank = &s->bank[bank_no];
+        line_no = (offset & 0x7f) >> 2;
+        bank->priority[line_no] = (value >> 2) & 0x3f;
+        bank->fiq &= ~(1 << line_no);
+        bank->fiq |= (value & 1) << line_no;
+        return;
+
+    case 0x00:	/* INTC_REVISION */
+    case 0x14:	/* INTC_SYSSTATUS */
+    case 0x40:	/* INTC_SIR_IRQ */
+    case 0x44:	/* INTC_SIR_FIQ */
+    case 0x80:	/* INTC_ITR */
+    case 0x98:	/* INTC_PENDING_IRQ */
+    case 0x9c:	/* INTC_PENDING_FIQ */
+        OMAP_RO_REG(addr);
+        return;
+    }
+    OMAP_BAD_REG(addr);
+}
+
+static CPUReadMemoryFunc *omap2_inth_readfn[] = {
+    omap_badwidth_read32,
+    omap_badwidth_read32,
+    omap2_inth_read,
+};
+
+static CPUWriteMemoryFunc *omap2_inth_writefn[] = {
+    omap2_inth_write,
+    omap2_inth_write,
+    omap2_inth_write,
+};
+
+struct omap_intr_handler_s *omap2_inth_init(target_phys_addr_t base,
+                int size, int nbanks, qemu_irq **pins,
+                qemu_irq parent_irq, qemu_irq parent_fiq,
+                omap_clk fclk, omap_clk iclk)
+{
+    int iomemtype;
+    struct omap_intr_handler_s *s = (struct omap_intr_handler_s *)
+            qemu_mallocz(sizeof(struct omap_intr_handler_s) +
+                            sizeof(struct omap_intr_handler_bank_s) * nbanks);
+
+    s->parent_intr[0] = parent_irq;
+    s->parent_intr[1] = parent_fiq;
+    s->base = base;
+    s->nbanks = nbanks;
+    s->level_only = 1;
+    s->pins = qemu_allocate_irqs(omap_set_intr_noedge, s, nbanks * 32);
+    if (pins)
+        *pins = s->pins;
+
+    omap_inth_reset(s);
+
+    iomemtype = cpu_register_io_memory(0, omap2_inth_readfn,
+                    omap2_inth_writefn, s);
+    cpu_register_physical_memory(s->base, size, iomemtype);
+
+    return s;
+}
+
 /* MPU OS timers */
 struct omap_mpu_timer_s {
     qemu_irq irq;
@@ -1289,6 +1543,8 @@ static uint32_t omap_id_read(void *opaque, target_phys_addr_t addr)
             return 0x03310315;
         case omap1510:
             return 0x03310115;
+        default:
+            cpu_abort(cpu_single_env, "%s: bad mpu model\n", __FUNCTION__);
         }
         break;
 
@@ -1298,6 +1554,8 @@ static uint32_t omap_id_read(void *opaque, target_phys_addr_t addr)
             return 0xfb57402f;
         case omap1510:
             return 0xfb47002f;
+        default:
+            cpu_abort(cpu_single_env, "%s: bad mpu model\n", __FUNCTION__);
         }
         break;
     }
@@ -1722,19 +1980,116 @@ static void omap_dpll_init(struct dpll_ctl_s *s, target_phys_addr_t base,
 /* UARTs */
 struct omap_uart_s {
     SerialState *serial; /* TODO */
+    struct omap_target_agent_s *ta;
+    target_phys_addr_t base;
+
+    uint8_t eblr;
+    uint8_t syscontrol;
+    uint8_t wkup;
+    uint8_t cfps;
 };
 
-static void omap_uart_reset(struct omap_uart_s *s)
+void omap_uart_reset(struct omap_uart_s *s)
 {
+    s->eblr = 0x00;
+    s->syscontrol = 0;
+    s->wkup = 0x3f;
+    s->cfps = 0x69;
 }
 
 struct omap_uart_s *omap_uart_init(target_phys_addr_t base,
-                qemu_irq irq, omap_clk clk, CharDriverState *chr)
+                qemu_irq irq, omap_clk fclk, omap_clk iclk,
+                qemu_irq txdma, qemu_irq rxdma, CharDriverState *chr)
 {
     struct omap_uart_s *s = (struct omap_uart_s *)
             qemu_mallocz(sizeof(struct omap_uart_s));
-    if (chr)
-        s->serial = serial_mm_init(base, 2, irq, chr, 1);
+
+    s->serial = serial_mm_init(base, 2, irq, chr ?: qemu_chr_open("null"), 1);
+
+    return s;
+}
+
+static uint32_t omap_uart_read(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_uart_s *s = (struct omap_uart_s *) opaque;
+    int offset = addr - s->base;
+
+    switch (offset) {
+    case 0x48:	/* EBLR */
+        return s->eblr;
+    case 0x50:	/* MVR */
+        return 0x30;
+    case 0x54:	/* SYSC */
+        return s->syscontrol;
+    case 0x58:	/* SYSS */
+        return 1;
+    case 0x5c:	/* WER */
+        return s->wkup;
+    case 0x60:	/* CFPS */
+        return s->cfps;
+    }
+
+    OMAP_BAD_REG(addr);
+    return 0;
+}
+
+static void omap_uart_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct omap_uart_s *s = (struct omap_uart_s *) opaque;
+    int offset = addr - s->base;
+
+    switch (offset) {
+    case 0x48:	/* EBLR */
+        s->eblr = value & 0xff;
+        break;
+    case 0x50:	/* MVR */
+    case 0x58:	/* SYSS */
+        OMAP_RO_REG(addr);
+        break;
+    case 0x54:	/* SYSC */
+        s->syscontrol = value & 0x1d;
+        if (value & 2)
+            omap_uart_reset(s);
+        break;
+    case 0x5c:	/* WER */
+        s->wkup = value & 0x7f;
+        break;
+    case 0x60:	/* CFPS */
+        s->cfps = value & 0xff;
+        break;
+    default:
+        OMAP_BAD_REG(addr);
+    }
+}
+
+static CPUReadMemoryFunc *omap_uart_readfn[] = {
+    omap_uart_read,
+    omap_uart_read,
+    omap_badwidth_read8,
+};
+
+static CPUWriteMemoryFunc *omap_uart_writefn[] = {
+    omap_uart_write,
+    omap_uart_write,
+    omap_badwidth_write8,
+};
+
+struct omap_uart_s *omap2_uart_init(struct omap_target_agent_s *ta,
+                qemu_irq irq, omap_clk fclk, omap_clk iclk,
+                qemu_irq txdma, qemu_irq rxdma, CharDriverState *chr)
+{
+    target_phys_addr_t base = omap_l4_attach(ta, 0, 0);
+    struct omap_uart_s *s = omap_uart_init(base, irq,
+                    fclk, iclk, txdma, rxdma, chr);
+    int iomemtype = cpu_register_io_memory(0, omap_uart_readfn,
+                    omap_uart_writefn, s);
+
+    s->ta = ta;
+    s->base = base;
+
+    cpu_register_physical_memory(s->base + 0x20, 0x100, iomemtype);
+
     return s;
 }
 
@@ -2778,9 +3133,11 @@ struct omap_uwire_s *omap_uwire_init(target_phys_addr_t base,
 void omap_uwire_attach(struct omap_uwire_s *s,
                 struct uwire_slave_s *slave, int chipselect)
 {
-    if (chipselect < 0 || chipselect > 3)
-        cpu_abort(cpu_single_env, "%s: Bad chipselect %i\n", __FUNCTION__,
-                        chipselect);
+    if (chipselect < 0 || chipselect > 3) {
+        fprintf(stderr, "%s: Bad chipselect %i\n",
+                        __FUNCTION__, chipselect);
+        exit(-1);
+    }
 
     s->chip[chipselect] = slave;
 }
@@ -4123,7 +4481,7 @@ static void omap_setup_mpui_io(struct omap_mpu_state_s *mpu)
 }
 
 /* General chip reset */
-static void omap_mpu_reset(void *opaque)
+static void omap1_mpu_reset(void *opaque)
 {
     struct omap_mpu_state_s *mpu = (struct omap_mpu_state_s *) opaque;
 
@@ -4153,7 +4511,7 @@ static void omap_mpu_reset(void *opaque)
     omap_uwire_reset(mpu->microwire);
     omap_pwl_reset(mpu);
     omap_pwt_reset(mpu);
-    omap_i2c_reset(mpu->i2c);
+    omap_i2c_reset(mpu->i2c[0]);
     omap_rtc_reset(mpu->rtc);
     omap_mcbsp_reset(mpu->mcbsp1);
     omap_mcbsp_reset(mpu->mcbsp2);
@@ -4205,7 +4563,7 @@ static void omap_setup_dsp_mapping(const struct omap_map_s *map)
     }
 }
 
-static void omap_mpu_wakeup(void *opaque, int irq, int req)
+void omap_mpu_wakeup(void *opaque, int irq, int req)
 {
     struct omap_mpu_state_s *mpu = (struct omap_mpu_state_s *) opaque;
 
@@ -4213,7 +4571,7 @@ static void omap_mpu_wakeup(void *opaque, int irq, int req)
         cpu_interrupt(mpu->env, CPU_INTERRUPT_EXITTB);
 }
 
-static const struct dma_irq_map omap_dma_irq_map[] = {
+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 },
@@ -4307,17 +4665,16 @@ struct omap_mpu_state_s *omap310_mpu_init(unsigned long sdram_size,
     omap_clkm_init(0xfffece00, 0xe1008000, s);
 
     cpu_irq = arm_pic_init_cpu(s->env);
-    s->ih[0] = omap_inth_init(0xfffecb00, 0x100, 1,
+    s->ih[0] = omap_inth_init(0xfffecb00, 0x100, 1, &s->irq[0],
                     cpu_irq[ARM_PIC_CPU_IRQ], cpu_irq[ARM_PIC_CPU_FIQ],
                     omap_findclk(s, "arminth_ck"));
-    s->ih[1] = omap_inth_init(0xfffe0000, 0x800, 1,
+    s->ih[1] = omap_inth_init(0xfffe0000, 0x800, 1, &s->irq[1],
                     s->ih[0]->pins[OMAP_INT_15XX_IH2_IRQ], NULL,
                     omap_findclk(s, "arminth_ck"));
-    s->irq[0] = s->ih[0]->pins;
-    s->irq[1] = s->ih[1]->pins;
 
     for (i = 0; i < 6; i ++)
-        dma_irqs[i] = s->irq[omap_dma_irq_map[i].ih][omap_dma_irq_map[i].intr];
+        dma_irqs[i] =
+                s->irq[omap1_dma_irq_map[i].ih][omap1_dma_irq_map[i].intr];
     s->dma = omap_dma_init(0xfffed800, dma_irqs, s->irq[0][OMAP_INT_DMA_LCD],
                            s, omap_findclk(s, "dma_ck"), omap_dma_3_1);
 
@@ -4367,12 +4724,18 @@ struct omap_mpu_state_s *omap310_mpu_init(unsigned long sdram_size,
 
     s->uart[0] = omap_uart_init(0xfffb0000, s->irq[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],
                     serial_hds[0]);
     s->uart[1] = omap_uart_init(0xfffb0800, s->irq[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],
                     serial_hds[0] ? serial_hds[1] : 0);
     s->uart[2] = omap_uart_init(0xe1019800, s->irq[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],
                     serial_hds[0] && serial_hds[1] ? serial_hds[2] : 0);
 
     omap_dpll_init(&s->dpll[0], 0xfffecf00, omap_findclk(s, "dpll1"));
@@ -4401,7 +4764,7 @@ struct omap_mpu_state_s *omap310_mpu_init(unsigned long sdram_size,
     omap_pwl_init(0xfffb5800, s, omap_findclk(s, "armxor_ck"));
     omap_pwt_init(0xfffb6000, s, omap_findclk(s, "armxor_ck"));
 
-    s->i2c = omap_i2c_init(0xfffb3800, s->irq[1][OMAP_INT_I2C],
+    s->i2c[0] = omap_i2c_init(0xfffb3800, s->irq[1][OMAP_INT_I2C],
                     &s->drq[OMAP_DMA_I2C_RX], omap_findclk(s, "mpuper_ck"));
 
     s->rtc = omap_rtc_init(0xfffb4800, &s->irq[1][OMAP_INT_RTC_TIMER],
@@ -4435,7 +4798,7 @@ struct omap_mpu_state_s *omap310_mpu_init(unsigned long sdram_size,
     omap_setup_dsp_mapping(omap15xx_dsp_mm);
     omap_setup_mpui_io(s);
 
-    qemu_register_reset(omap_mpu_reset, s);
+    qemu_register_reset(omap1_mpu_reset, s);
 
     return s;
 }
diff --git a/hw/omap2.c b/hw/omap2.c
new file mode 100644
index 0000000..1e51197
--- /dev/null
+++ b/hw/omap2.c
@@ -0,0 +1,3872 @@
+/*
+ * 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 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+#include "hw.h"
+#include "arm-misc.h"
+#include "omap.h"
+#include "sysemu.h"
+#include "qemu-timer.h"
+#include "qemu-char.h"
+#include "flash.h"
+/* We use pc-style serial ports.  */
+#include "pc.h"
+
+/* GP timers */
+struct omap_gp_timer_s {
+    qemu_irq irq;
+    qemu_irq wkup;
+    qemu_irq in;
+    qemu_irq out;
+    omap_clk clk;
+    target_phys_addr_t base;
+    QEMUTimer *timer;
+    QEMUTimer *match;
+    struct omap_target_agent_s *ta;
+
+    int in_val;
+    int out_val;
+    int64_t time;
+    int64_t rate;
+    int64_t ticks_per_sec;
+
+    int16_t config;
+    int status;
+    int it_ena;
+    int wu_ena;
+    int enable;
+    int inout;
+    int capt2;
+    int pt;
+    enum {
+        gpt_trigger_none, gpt_trigger_overflow, gpt_trigger_both
+    } trigger;
+    enum {
+        gpt_capture_none, gpt_capture_rising,
+        gpt_capture_falling, gpt_capture_both
+    } capture;
+    int scpwm;
+    int ce;
+    int pre;
+    int ptv;
+    int ar;
+    int st;
+    int posted;
+    uint32_t val;
+    uint32_t load_val;
+    uint32_t capture_val[2];
+    uint32_t match_val;
+    int capt_num;
+
+    uint16_t writeh;	/* LSB */
+    uint16_t readh;	/* MSB */
+};
+
+#define GPT_TCAR_IT	(1 << 2)
+#define GPT_OVF_IT	(1 << 1)
+#define GPT_MAT_IT	(1 << 0)
+
+static inline void omap_gp_timer_intr(struct omap_gp_timer_s *timer, int it)
+{
+    if (timer->it_ena & it) {
+        if (!timer->status)
+            qemu_irq_raise(timer->irq);
+
+        timer->status |= it;
+        /* Or are the status bits set even when masked?
+         * i.e. is masking applied before or after the status register?  */
+    }
+
+    if (timer->wu_ena & it)
+        qemu_irq_pulse(timer->wkup);
+}
+
+static inline void omap_gp_timer_out(struct omap_gp_timer_s *timer, int level)
+{
+    if (!timer->inout && timer->out_val != level) {
+        timer->out_val = level;
+        qemu_set_irq(timer->out, level);
+    }
+}
+
+static inline uint32_t omap_gp_timer_read(struct omap_gp_timer_s *timer)
+{
+    uint64_t distance;
+
+    if (timer->st && timer->rate) {
+        distance = qemu_get_clock(vm_clock) - timer->time;
+        distance = muldiv64(distance, timer->rate, timer->ticks_per_sec);
+
+        if (distance >= 0xffffffff - timer->val)
+            return 0xffffffff;
+        else
+            return timer->val + distance;
+    } else
+        return timer->val;
+}
+
+static inline void omap_gp_timer_sync(struct omap_gp_timer_s *timer)
+{
+    if (timer->st) {
+        timer->val = omap_gp_timer_read(timer);
+        timer->time = qemu_get_clock(vm_clock);
+    }
+}
+
+static inline void omap_gp_timer_update(struct omap_gp_timer_s *timer)
+{
+    int64_t expires, matches;
+
+    if (timer->st && timer->rate) {
+        expires = muldiv64(0x100000000ll - timer->val,
+                        timer->ticks_per_sec, timer->rate);
+        qemu_mod_timer(timer->timer, timer->time + expires);
+
+        if (timer->ce && timer->match_val >= timer->val) {
+            matches = muldiv64(timer->match_val - timer->val,
+                            timer->ticks_per_sec, timer->rate);
+            qemu_mod_timer(timer->match, timer->time + matches);
+        } else
+            qemu_del_timer(timer->match);
+    } else {
+        qemu_del_timer(timer->timer);
+        qemu_del_timer(timer->match);
+        omap_gp_timer_out(timer, timer->scpwm);
+    }
+}
+
+static inline void omap_gp_timer_trigger(struct omap_gp_timer_s *timer)
+{
+    if (timer->pt)
+        /* TODO in overflow-and-match mode if the first event to
+         * occurs is the match, don't toggle.  */
+        omap_gp_timer_out(timer, !timer->out_val);
+    else
+        /* TODO inverted pulse on timer->out_val == 1?  */
+        qemu_irq_pulse(timer->out);
+}
+
+static void omap_gp_timer_tick(void *opaque)
+{
+    struct omap_gp_timer_s *timer = (struct omap_gp_timer_s *) opaque;
+
+    if (!timer->ar) {
+        timer->st = 0;
+        timer->val = 0;
+    } else {
+        timer->val = timer->load_val;
+        timer->time = qemu_get_clock(vm_clock);
+    }
+
+    if (timer->trigger == gpt_trigger_overflow ||
+                    timer->trigger == gpt_trigger_both)
+        omap_gp_timer_trigger(timer);
+
+    omap_gp_timer_intr(timer, GPT_OVF_IT);
+    omap_gp_timer_update(timer);
+}
+
+static void omap_gp_timer_match(void *opaque)
+{
+    struct omap_gp_timer_s *timer = (struct omap_gp_timer_s *) opaque;
+
+    if (timer->trigger == gpt_trigger_both)
+        omap_gp_timer_trigger(timer);
+
+    omap_gp_timer_intr(timer, GPT_MAT_IT);
+}
+
+static void omap_gp_timer_input(void *opaque, int line, int on)
+{
+    struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque;
+    int trigger;
+
+    switch (s->capture) {
+    default:
+    case gpt_capture_none:
+        trigger = 0;
+        break;
+    case gpt_capture_rising:
+        trigger = !s->in_val && on;
+        break;
+    case gpt_capture_falling:
+        trigger = s->in_val && !on;
+        break;
+    case gpt_capture_both:
+        trigger = (s->in_val == !on);
+        break;
+    }
+    s->in_val = on;
+
+    if (s->inout && trigger && s->capt_num < 2) {
+        s->capture_val[s->capt_num] = omap_gp_timer_read(s);
+
+        if (s->capt2 == s->capt_num ++)
+            omap_gp_timer_intr(s, GPT_TCAR_IT);
+    }
+}
+
+static void omap_gp_timer_clk_update(void *opaque, int line, int on)
+{
+    struct omap_gp_timer_s *timer = (struct omap_gp_timer_s *) opaque;
+
+    omap_gp_timer_sync(timer);
+    timer->rate = on ? omap_clk_getrate(timer->clk) : 0;
+    omap_gp_timer_update(timer);
+}
+
+static void omap_gp_timer_clk_setup(struct omap_gp_timer_s *timer)
+{
+    omap_clk_adduser(timer->clk,
+                    qemu_allocate_irqs(omap_gp_timer_clk_update, timer, 1)[0]);
+    timer->rate = omap_clk_getrate(timer->clk);
+}
+
+static void omap_gp_timer_reset(struct omap_gp_timer_s *s)
+{
+    s->config = 0x000;
+    s->status = 0;
+    s->it_ena = 0;
+    s->wu_ena = 0;
+    s->inout = 0;
+    s->capt2 = 0;
+    s->capt_num = 0;
+    s->pt = 0;
+    s->trigger = gpt_trigger_none;
+    s->capture = gpt_capture_none;
+    s->scpwm = 0;
+    s->ce = 0;
+    s->pre = 0;
+    s->ptv = 0;
+    s->ar = 0;
+    s->st = 0;
+    s->posted = 1;
+    s->val = 0x00000000;
+    s->load_val = 0x00000000;
+    s->capture_val[0] = 0x00000000;
+    s->capture_val[1] = 0x00000000;
+    s->match_val = 0x00000000;
+    omap_gp_timer_update(s);
+}
+
+static uint32_t omap_gp_timer_readw(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque;
+    int offset = addr - s->base;
+
+    switch (offset) {
+    case 0x00:	/* TIDR */
+        return 0x21;
+
+    case 0x10:	/* TIOCP_CFG */
+        return s->config;
+
+    case 0x14:	/* TISTAT */
+        /* ??? When's this bit reset? */
+        return 1;						/* RESETDONE */
+
+    case 0x18:	/* TISR */
+        return s->status;
+
+    case 0x1c:	/* TIER */
+        return s->it_ena;
+
+    case 0x20:	/* TWER */
+        return s->wu_ena;
+
+    case 0x24:	/* TCLR */
+        return (s->inout << 14) |
+                (s->capt2 << 13) |
+                (s->pt << 12) |
+                (s->trigger << 10) |
+                (s->capture << 8) |
+                (s->scpwm << 7) |
+                (s->ce << 6) |
+                (s->pre << 5) |
+                (s->ptv << 2) |
+                (s->ar << 1) |
+                (s->st << 0);
+
+    case 0x28:	/* TCRR */
+        return omap_gp_timer_read(s);
+
+    case 0x2c:	/* TLDR */
+        return s->load_val;
+
+    case 0x30:	/* TTGR */
+        return 0xffffffff;
+
+    case 0x34:	/* TWPS */
+        return 0x00000000;	/* No posted writes pending.  */
+
+    case 0x38:	/* TMAR */
+        return s->match_val;
+
+    case 0x3c:	/* TCAR1 */
+        return s->capture_val[0];
+
+    case 0x40:	/* TSICR */
+        return s->posted << 2;
+
+    case 0x44:	/* TCAR2 */
+        return s->capture_val[1];
+    }
+
+    OMAP_BAD_REG(addr);
+    return 0;
+}
+
+static uint32_t omap_gp_timer_readh(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque;
+    uint32_t ret;
+
+    if (addr & 2)
+        return s->readh;
+    else {
+        ret = omap_gp_timer_readw(opaque, addr);
+        s->readh = ret >> 16;
+        return ret & 0xffff;
+    }
+}
+
+static CPUReadMemoryFunc *omap_gp_timer_readfn[] = {
+    omap_badwidth_read32,
+    omap_gp_timer_readh,
+    omap_gp_timer_readw,
+};
+
+static void omap_gp_timer_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque;
+    int offset = addr - s->base;
+
+    switch (offset) {
+    case 0x00:	/* TIDR */
+    case 0x14:	/* TISTAT */
+    case 0x34:	/* TWPS */
+    case 0x3c:	/* TCAR1 */
+    case 0x44:	/* TCAR2 */
+        OMAP_RO_REG(addr);
+        break;
+
+    case 0x10:	/* TIOCP_CFG */
+        s->config = value & 0x33d;
+        if (((value >> 3) & 3) == 3)				/* IDLEMODE */
+            fprintf(stderr, "%s: illegal IDLEMODE value in TIOCP_CFG\n",
+                            __FUNCTION__);
+        if (value & 2)						/* SOFTRESET */
+            omap_gp_timer_reset(s);
+        break;
+
+    case 0x18:	/* TISR */
+        if (value & GPT_TCAR_IT)
+            s->capt_num = 0;
+        if (s->status && !(s->status &= ~value))
+            qemu_irq_lower(s->irq);
+        break;
+
+    case 0x1c:	/* TIER */
+        s->it_ena = value & 7;
+        break;
+
+    case 0x20:	/* TWER */
+        s->wu_ena = value & 7;
+        break;
+
+    case 0x24:	/* TCLR */
+        omap_gp_timer_sync(s);
+        s->inout = (value >> 14) & 1;
+        s->capt2 = (value >> 13) & 1;
+        s->pt = (value >> 12) & 1;
+        s->trigger = (value >> 10) & 3;
+        if (s->capture == gpt_capture_none &&
+                        ((value >> 8) & 3) != gpt_capture_none)
+            s->capt_num = 0;
+        s->capture = (value >> 8) & 3;
+        s->scpwm = (value >> 7) & 1;
+        s->ce = (value >> 6) & 1;
+        s->pre = (value >> 5) & 1;
+        s->ptv = (value >> 2) & 7;
+        s->ar = (value >> 1) & 1;
+        s->st = (value >> 0) & 1;
+        if (s->inout && s->trigger != gpt_trigger_none)
+            fprintf(stderr, "%s: GP timer pin must be an output "
+                            "for this trigger mode\n", __FUNCTION__);
+        if (!s->inout && s->capture != gpt_capture_none)
+            fprintf(stderr, "%s: GP timer pin must be an input "
+                            "for this capture mode\n", __FUNCTION__);
+        if (s->trigger == gpt_trigger_none)
+            omap_gp_timer_out(s, s->scpwm);
+        /* TODO: make sure this doesn't overflow 32-bits */
+        s->ticks_per_sec = ticks_per_sec << (s->pre ? s->ptv + 1 : 0);
+        omap_gp_timer_update(s);
+        break;
+
+    case 0x28:	/* TCRR */
+        s->time = qemu_get_clock(vm_clock);
+        s->val = value;
+        omap_gp_timer_update(s);
+        break;
+
+    case 0x2c:	/* TLDR */
+        s->load_val = value;
+        break;
+
+    case 0x30:	/* TTGR */
+        s->time = qemu_get_clock(vm_clock);
+        s->val = s->load_val;
+        omap_gp_timer_update(s);
+        break;
+
+    case 0x38:	/* TMAR */
+        omap_gp_timer_sync(s);
+        s->match_val = value;
+        omap_gp_timer_update(s);
+        break;
+
+    case 0x40:	/* TSICR */
+        s->posted = (value >> 2) & 1;
+        if (value & 2)	/* How much exactly are we supposed to reset? */
+            omap_gp_timer_reset(s);
+        break;
+
+    default:
+        OMAP_BAD_REG(addr);
+    }
+}
+
+static void omap_gp_timer_writeh(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque;
+
+    if (addr & 2)
+        return omap_gp_timer_write(opaque, addr, (value << 16) | s->writeh);
+    else
+        s->writeh = (uint16_t) value;
+}
+
+static CPUWriteMemoryFunc *omap_gp_timer_writefn[] = {
+    omap_badwidth_write32,
+    omap_gp_timer_writeh,
+    omap_gp_timer_write,
+};
+
+struct omap_gp_timer_s *omap_gp_timer_init(struct omap_target_agent_s *ta,
+                qemu_irq irq, omap_clk fclk, omap_clk iclk)
+{
+    int iomemtype;
+    struct omap_gp_timer_s *s = (struct omap_gp_timer_s *)
+            qemu_mallocz(sizeof(struct omap_gp_timer_s));
+
+    s->ta = ta;
+    s->irq = irq;
+    s->clk = fclk;
+    s->timer = qemu_new_timer(vm_clock, omap_gp_timer_tick, s);
+    s->match = qemu_new_timer(vm_clock, omap_gp_timer_match, s);
+    s->in = qemu_allocate_irqs(omap_gp_timer_input, s, 1)[0];
+    omap_gp_timer_reset(s);
+    omap_gp_timer_clk_setup(s);
+
+    iomemtype = cpu_register_io_memory(0, omap_gp_timer_readfn,
+                    omap_gp_timer_writefn, s);
+    s->base = omap_l4_attach(ta, 0, iomemtype);
+
+    return s;
+}
+
+/* 32-kHz Sync Timer of the OMAP2 */
+static uint32_t omap_synctimer_read(struct omap_synctimer_s *s) {
+    return muldiv64(qemu_get_clock(vm_clock), 0x8000, ticks_per_sec);
+}
+
+static void omap_synctimer_reset(struct omap_synctimer_s *s)
+{
+    s->val = omap_synctimer_read(s);
+}
+
+static uint32_t omap_synctimer_readw(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_synctimer_s *s = (struct omap_synctimer_s *) opaque;
+    int offset = addr - s->base;
+
+    switch (offset) {
+    case 0x00:	/* 32KSYNCNT_REV */
+        return 0x21;
+
+    case 0x10:	/* CR */
+        return omap_synctimer_read(s) - s->val;
+    }
+
+    OMAP_BAD_REG(addr);
+    return 0;
+}
+
+static uint32_t omap_synctimer_readh(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_synctimer_s *s = (struct omap_synctimer_s *) opaque;
+    uint32_t ret;
+
+    if (addr & 2)
+        return s->readh;
+    else {
+        ret = omap_synctimer_readw(opaque, addr);
+        s->readh = ret >> 16;
+        return ret & 0xffff;
+    }
+}
+
+static CPUReadMemoryFunc *omap_synctimer_readfn[] = {
+    omap_badwidth_read32,
+    omap_synctimer_readh,
+    omap_synctimer_readw,
+};
+
+static void omap_synctimer_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    OMAP_BAD_REG(addr);
+}
+
+static CPUWriteMemoryFunc *omap_synctimer_writefn[] = {
+    omap_badwidth_write32,
+    omap_synctimer_write,
+    omap_synctimer_write,
+};
+
+void omap_synctimer_init(struct omap_target_agent_s *ta,
+                struct omap_mpu_state_s *mpu, omap_clk fclk, omap_clk iclk)
+{
+    struct omap_synctimer_s *s = &mpu->synctimer;
+
+    omap_synctimer_reset(s);
+    s->base = omap_l4_attach(ta, 0, cpu_register_io_memory(0,
+                            omap_synctimer_readfn, omap_synctimer_writefn, s));
+}
+
+/* General-Purpose Interface of OMAP2 */
+struct omap2_gpio_s {
+    target_phys_addr_t base;
+    qemu_irq irq[2];
+    qemu_irq wkup;
+    qemu_irq *in;
+    qemu_irq handler[32];
+
+    uint8_t config[2];
+    uint32_t inputs;
+    uint32_t outputs;
+    uint32_t dir;
+    uint32_t level[2];
+    uint32_t edge[2];
+    uint32_t mask[2];
+    uint32_t wumask;
+    uint32_t ints[2];
+    uint32_t debounce;
+    uint8_t delay;
+};
+
+static inline void omap_gpio_module_int_update(struct omap2_gpio_s *s,
+                int line)
+{
+    qemu_set_irq(s->irq[line], s->ints[line] & s->mask[line]);
+}
+
+static void omap_gpio_module_wake(struct omap2_gpio_s *s, int line)
+{
+    if (!(s->config[0] & (1 << 2)))			/* ENAWAKEUP */
+        return;
+    if (!(s->config[0] & (3 << 3)))			/* Force Idle */
+        return;
+    if (!(s->wumask & (1 << line)))
+        return;
+
+    qemu_irq_raise(s->wkup);
+}
+
+static inline void omap_gpio_module_out_update(struct omap2_gpio_s *s,
+                uint32_t diff)
+{
+    int ln;
+
+    s->outputs ^= diff;
+    diff &= ~s->dir;
+    while ((ln = ffs(diff))) {
+        ln --;
+        qemu_set_irq(s->handler[ln], (s->outputs >> ln) & 1);
+        diff &= ~(1 << ln);
+    }
+}
+
+static void omap_gpio_module_level_update(struct omap2_gpio_s *s, int line)
+{
+    s->ints[line] |= s->dir &
+            ((s->inputs & s->level[1]) | (~s->inputs & s->level[0]));
+    omap_gpio_module_int_update(s, line);
+}
+
+static inline void omap_gpio_module_int(struct omap2_gpio_s *s, int line)
+{
+    s->ints[0] |= 1 << line;
+    omap_gpio_module_int_update(s, 0);
+    s->ints[1] |= 1 << line;
+    omap_gpio_module_int_update(s, 1);
+    omap_gpio_module_wake(s, line);
+}
+
+static void omap_gpio_module_set(void *opaque, int line, int level)
+{
+    struct omap2_gpio_s *s = (struct omap2_gpio_s *) opaque;
+
+    if (level) {
+        if (s->dir & (1 << line) & ((~s->inputs & s->edge[0]) | s->level[1]))
+            omap_gpio_module_int(s, line);
+        s->inputs |= 1 << line;
+    } else {
+        if (s->dir & (1 << line) & ((s->inputs & s->edge[1]) | s->level[0]))
+            omap_gpio_module_int(s, line);
+        s->inputs &= ~(1 << line);
+    }
+}
+
+static void omap_gpio_module_reset(struct omap2_gpio_s *s)
+{
+    s->config[0] = 0;
+    s->config[1] = 2;
+    s->ints[0] = 0;
+    s->ints[1] = 0;
+    s->mask[0] = 0;
+    s->mask[1] = 0;
+    s->wumask = 0;
+    s->dir = ~0;
+    s->level[0] = 0;
+    s->level[1] = 0;
+    s->edge[0] = 0;
+    s->edge[1] = 0;
+    s->debounce = 0;
+    s->delay = 0;
+}
+
+static uint32_t omap_gpio_module_read(void *opaque, target_phys_addr_t addr)
+{
+    struct omap2_gpio_s *s = (struct omap2_gpio_s *) opaque;
+    int offset = addr - s->base;
+
+    switch (offset) {
+    case 0x00:	/* GPIO_REVISION */
+        return 0x18;
+
+    case 0x10:	/* GPIO_SYSCONFIG */
+        return s->config[0];
+
+    case 0x14:	/* GPIO_SYSSTATUS */
+        return 0x01;
+
+    case 0x18:	/* GPIO_IRQSTATUS1 */
+        return s->ints[0];
+
+    case 0x1c:	/* GPIO_IRQENABLE1 */
+    case 0x60:	/* GPIO_CLEARIRQENABLE1 */
+    case 0x64:	/* GPIO_SETIRQENABLE1 */
+        return s->mask[0];
+
+    case 0x20:	/* GPIO_WAKEUPENABLE */
+    case 0x80:	/* GPIO_CLEARWKUENA */
+    case 0x84:	/* GPIO_SETWKUENA */
+        return s->wumask;
+
+    case 0x28:	/* GPIO_IRQSTATUS2 */
+        return s->ints[1];
+
+    case 0x2c:	/* GPIO_IRQENABLE2 */
+    case 0x70:	/* GPIO_CLEARIRQENABLE2 */
+    case 0x74:	/* GPIO_SETIREQNEABLE2 */
+        return s->mask[1];
+
+    case 0x30:	/* GPIO_CTRL */
+        return s->config[1];
+
+    case 0x34:	/* GPIO_OE */
+        return s->dir;
+
+    case 0x38:	/* GPIO_DATAIN */
+        return s->inputs;
+
+    case 0x3c:	/* GPIO_DATAOUT */
+    case 0x90:	/* GPIO_CLEARDATAOUT */
+    case 0x94:	/* GPIO_SETDATAOUT */
+        return s->outputs;
+
+    case 0x40:	/* GPIO_LEVELDETECT0 */
+        return s->level[0];
+
+    case 0x44:	/* GPIO_LEVELDETECT1 */
+        return s->level[1];
+
+    case 0x48:	/* GPIO_RISINGDETECT */
+        return s->edge[0];
+
+    case 0x4c:	/* GPIO_FALLINGDETECT */
+        return s->edge[1];
+
+    case 0x50:	/* GPIO_DEBOUNCENABLE */
+        return s->debounce;
+
+    case 0x54:	/* GPIO_DEBOUNCINGTIME */
+        return s->delay;
+    }
+
+    OMAP_BAD_REG(addr);
+    return 0;
+}
+
+static void omap_gpio_module_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct omap2_gpio_s *s = (struct omap2_gpio_s *) opaque;
+    int offset = addr - s->base;
+    uint32_t diff;
+    int ln;
+
+    switch (offset) {
+    case 0x00:	/* GPIO_REVISION */
+    case 0x14:	/* GPIO_SYSSTATUS */
+    case 0x38:	/* GPIO_DATAIN */
+        OMAP_RO_REG(addr);
+        break;
+
+    case 0x10:	/* GPIO_SYSCONFIG */
+        if (((value >> 3) & 3) == 3)
+            fprintf(stderr, "%s: bad IDLEMODE value\n", __FUNCTION__);
+        if (value & 2)
+            omap_gpio_module_reset(s);
+        s->config[0] = value & 0x1d;
+        break;
+
+    case 0x18:	/* GPIO_IRQSTATUS1 */
+        if (s->ints[0] & value) {
+            s->ints[0] &= ~value;
+            omap_gpio_module_level_update(s, 0);
+        }
+        break;
+
+    case 0x1c:	/* GPIO_IRQENABLE1 */
+        s->mask[0] = value;
+        omap_gpio_module_int_update(s, 0);
+        break;
+
+    case 0x20:	/* GPIO_WAKEUPENABLE */
+        s->wumask = value;
+        break;
+
+    case 0x28:	/* GPIO_IRQSTATUS2 */
+        if (s->ints[1] & value) {
+            s->ints[1] &= ~value;
+            omap_gpio_module_level_update(s, 1);
+        }
+        break;
+
+    case 0x2c:	/* GPIO_IRQENABLE2 */
+        s->mask[1] = value;
+        omap_gpio_module_int_update(s, 1);
+        break;
+
+    case 0x30:	/* GPIO_CTRL */
+        s->config[1] = value & 7;
+        break;
+
+    case 0x34:	/* GPIO_OE */
+        diff = s->outputs & (s->dir ^ value);
+        s->dir = value;
+
+        value = s->outputs & ~s->dir;
+        while ((ln = ffs(diff))) {
+            diff &= ~(1 <<-- ln);
+            qemu_set_irq(s->handler[ln], (value >> ln) & 1);
+        }
+
+        omap_gpio_module_level_update(s, 0);
+        omap_gpio_module_level_update(s, 1);
+        break;
+
+    case 0x3c:	/* GPIO_DATAOUT */
+        omap_gpio_module_out_update(s, s->outputs ^ value);
+        break;
+
+    case 0x40:	/* GPIO_LEVELDETECT0 */
+        s->level[0] = value;
+        omap_gpio_module_level_update(s, 0);
+        omap_gpio_module_level_update(s, 1);
+        break;
+
+    case 0x44:	/* GPIO_LEVELDETECT1 */
+        s->level[1] = value;
+        omap_gpio_module_level_update(s, 0);
+        omap_gpio_module_level_update(s, 1);
+        break;
+
+    case 0x48:	/* GPIO_RISINGDETECT */
+        s->edge[0] = value;
+        break;
+
+    case 0x4c:	/* GPIO_FALLINGDETECT */
+        s->edge[1] = value;
+        break;
+
+    case 0x50:	/* GPIO_DEBOUNCENABLE */
+        s->debounce = value;
+        break;
+
+    case 0x54:	/* GPIO_DEBOUNCINGTIME */
+        s->delay = value;
+        break;
+
+    case 0x60:	/* GPIO_CLEARIRQENABLE1 */
+        s->mask[0] &= ~value;
+        omap_gpio_module_int_update(s, 0);
+        break;
+
+    case 0x64:	/* GPIO_SETIRQENABLE1 */
+        s->mask[0] |= value;
+        omap_gpio_module_int_update(s, 0);
+        break;
+
+    case 0x70:	/* GPIO_CLEARIRQENABLE2 */
+        s->mask[1] &= ~value;
+        omap_gpio_module_int_update(s, 1);
+        break;
+
+    case 0x74:	/* GPIO_SETIREQNEABLE2 */
+        s->mask[1] |= value;
+        omap_gpio_module_int_update(s, 1);
+        break;
+
+    case 0x80:	/* GPIO_CLEARWKUENA */
+        s->wumask &= ~value;
+        break;
+
+    case 0x84:	/* GPIO_SETWKUENA */
+        s->wumask |= value;
+        break;
+
+    case 0x90:	/* GPIO_CLEARDATAOUT */
+        omap_gpio_module_out_update(s, s->outputs & value);
+        break;
+
+    case 0x94:	/* GPIO_SETDATAOUT */
+        omap_gpio_module_out_update(s, ~s->outputs & value);
+        break;
+
+    default:
+        OMAP_BAD_REG(addr);
+        return;
+    }
+}
+
+static uint32_t omap_gpio_module_readp(void *opaque, target_phys_addr_t addr)
+{
+    return omap_gpio_module_readp(opaque, addr) >> ((addr & 3) << 3);
+}
+
+static void omap_gpio_module_writep(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct omap2_gpio_s *s = (struct omap2_gpio_s *) opaque;
+    int offset = addr - s->base;
+    uint32_t cur = 0;
+    uint32_t mask = 0xffff;
+
+    switch (offset & ~3) {
+    case 0x00:	/* GPIO_REVISION */
+    case 0x14:	/* GPIO_SYSSTATUS */
+    case 0x38:	/* GPIO_DATAIN */
+        OMAP_RO_REG(addr);
+        break;
+
+    case 0x10:	/* GPIO_SYSCONFIG */
+    case 0x1c:	/* GPIO_IRQENABLE1 */
+    case 0x20:	/* GPIO_WAKEUPENABLE */
+    case 0x2c:	/* GPIO_IRQENABLE2 */
+    case 0x30:	/* GPIO_CTRL */
+    case 0x34:	/* GPIO_OE */
+    case 0x3c:	/* GPIO_DATAOUT */
+    case 0x40:	/* GPIO_LEVELDETECT0 */
+    case 0x44:	/* GPIO_LEVELDETECT1 */
+    case 0x48:	/* GPIO_RISINGDETECT */
+    case 0x4c:	/* GPIO_FALLINGDETECT */
+    case 0x50:	/* GPIO_DEBOUNCENABLE */
+    case 0x54:	/* GPIO_DEBOUNCINGTIME */
+        cur = omap_gpio_module_read(opaque, addr & ~3) &
+                ~(mask << ((addr & 3) << 3));
+
+        /* Fall through.  */
+    case 0x18:	/* GPIO_IRQSTATUS1 */
+    case 0x28:	/* GPIO_IRQSTATUS2 */
+    case 0x60:	/* GPIO_CLEARIRQENABLE1 */
+    case 0x64:	/* GPIO_SETIRQENABLE1 */
+    case 0x70:	/* GPIO_CLEARIRQENABLE2 */
+    case 0x74:	/* GPIO_SETIREQNEABLE2 */
+    case 0x80:	/* GPIO_CLEARWKUENA */
+    case 0x84:	/* GPIO_SETWKUENA */
+    case 0x90:	/* GPIO_CLEARDATAOUT */
+    case 0x94:	/* GPIO_SETDATAOUT */
+        value <<= (addr & 3) << 3;
+        omap_gpio_module_write(opaque, addr, cur | value);
+        break;
+
+    default:
+        OMAP_BAD_REG(addr);
+        return;
+    }
+}
+
+static CPUReadMemoryFunc *omap_gpio_module_readfn[] = {
+    omap_gpio_module_readp,
+    omap_gpio_module_readp,
+    omap_gpio_module_read,
+};
+
+static CPUWriteMemoryFunc *omap_gpio_module_writefn[] = {
+    omap_gpio_module_writep,
+    omap_gpio_module_writep,
+    omap_gpio_module_write,
+};
+
+static void omap_gpio_module_init(struct omap2_gpio_s *s,
+                struct omap_target_agent_s *ta, int region,
+                qemu_irq mpu, qemu_irq dsp, qemu_irq wkup,
+                omap_clk fclk, omap_clk iclk)
+{
+    int iomemtype;
+
+    s->irq[0] = mpu;
+    s->irq[1] = dsp;
+    s->wkup = wkup;
+    s->in = qemu_allocate_irqs(omap_gpio_module_set, s, 32);
+
+    iomemtype = cpu_register_io_memory(0, omap_gpio_module_readfn,
+                    omap_gpio_module_writefn, s);
+    s->base = omap_l4_attach(ta, region, iomemtype);
+}
+
+struct omap_gpif_s {
+    struct omap2_gpio_s module[5];
+    int modules;
+
+    target_phys_addr_t topbase;
+    int autoidle;
+    int gpo;
+};
+
+static void omap_gpif_reset(struct omap_gpif_s *s)
+{
+    int i;
+
+    for (i = 0; i < s->modules; i ++)
+        omap_gpio_module_reset(s->module + i);
+
+    s->autoidle = 0;
+    s->gpo = 0;
+}
+
+static uint32_t omap_gpif_top_read(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_gpif_s *s = (struct omap_gpif_s *) opaque;
+    int offset = addr - s->topbase;
+
+    switch (offset) {
+    case 0x00:	/* IPGENERICOCPSPL_REVISION */
+        return 0x18;
+
+    case 0x10:	/* IPGENERICOCPSPL_SYSCONFIG */
+        return s->autoidle;
+
+    case 0x14:	/* IPGENERICOCPSPL_SYSSTATUS */
+        return 0x01;
+
+    case 0x18:	/* IPGENERICOCPSPL_IRQSTATUS */
+        return 0x00;
+
+    case 0x40:	/* IPGENERICOCPSPL_GPO */
+        return s->gpo;
+
+    case 0x50:	/* IPGENERICOCPSPL_GPI */
+        return 0x00;
+    }
+
+    OMAP_BAD_REG(addr);
+    return 0;
+}
+
+static void omap_gpif_top_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct omap_gpif_s *s = (struct omap_gpif_s *) opaque;
+    int offset = addr - s->topbase;
+
+    switch (offset) {
+    case 0x00:	/* IPGENERICOCPSPL_REVISION */
+    case 0x14:	/* IPGENERICOCPSPL_SYSSTATUS */
+    case 0x18:	/* IPGENERICOCPSPL_IRQSTATUS */
+    case 0x50:	/* IPGENERICOCPSPL_GPI */
+        OMAP_RO_REG(addr);
+        break;
+
+    case 0x10:	/* IPGENERICOCPSPL_SYSCONFIG */
+        if (value & (1 << 1))					/* SOFTRESET */
+            omap_gpif_reset(s);
+        s->autoidle = value & 1;
+        break;
+
+    case 0x40:	/* IPGENERICOCPSPL_GPO */
+        s->gpo = value & 1;
+        break;
+
+    default:
+        OMAP_BAD_REG(addr);
+        return;
+    }
+}
+
+static CPUReadMemoryFunc *omap_gpif_top_readfn[] = {
+    omap_gpif_top_read,
+    omap_gpif_top_read,
+    omap_gpif_top_read,
+};
+
+static CPUWriteMemoryFunc *omap_gpif_top_writefn[] = {
+    omap_gpif_top_write,
+    omap_gpif_top_write,
+    omap_gpif_top_write,
+};
+
+struct omap_gpif_s *omap2_gpio_init(struct omap_target_agent_s *ta,
+                qemu_irq *irq, omap_clk *fclk, omap_clk iclk, int modules)
+{
+    int iomemtype, i;
+    struct omap_gpif_s *s = (struct omap_gpif_s *)
+            qemu_mallocz(sizeof(struct omap_gpif_s));
+    int region[4] = { 0, 2, 4, 5 };
+
+    s->modules = modules;
+    for (i = 0; i < modules; i ++)
+        omap_gpio_module_init(s->module + i, ta, region[i],
+                        irq[i], 0, 0, fclk[i], iclk);
+
+    omap_gpif_reset(s);
+
+    iomemtype = cpu_register_io_memory(0, omap_gpif_top_readfn,
+                    omap_gpif_top_writefn, s);
+    s->topbase = omap_l4_attach(ta, 1, iomemtype);
+
+    return s;
+}
+
+qemu_irq *omap2_gpio_in_get(struct omap_gpif_s *s, int start)
+{
+    if (start >= s->modules * 32 || start < 0)
+        cpu_abort(cpu_single_env, "%s: No GPIO line %i\n",
+                        __FUNCTION__, start);
+    return s->module[start >> 5].in + (start & 31);
+}
+
+void omap2_gpio_out_set(struct omap_gpif_s *s, int line, qemu_irq handler)
+{
+    if (line >= s->modules * 32 || line < 0)
+        cpu_abort(cpu_single_env, "%s: No GPIO line %i\n", __FUNCTION__, line);
+    s->module[line >> 5].handler[line & 31] = handler;
+}
+
+/* Multichannel SPI */
+struct omap_mcspi_s {
+    target_phys_addr_t base;
+    qemu_irq irq;
+    int chnum;
+
+    uint32_t sysconfig;
+    uint32_t systest;
+    uint32_t irqst;
+    uint32_t irqen;
+    uint32_t wken;
+    uint32_t control;
+
+    struct omap_mcspi_ch_s {
+        qemu_irq txdrq;
+        qemu_irq rxdrq;
+        uint32_t (*txrx)(void *opaque, uint32_t);
+        void *opaque;
+
+        uint32_t tx;
+        uint32_t rx;
+
+        uint32_t config;
+        uint32_t status;
+        uint32_t control;
+    } ch[4];
+};
+
+static inline void omap_mcspi_interrupt_update(struct omap_mcspi_s *s)
+{
+    qemu_set_irq(s->irq, s->irqst & s->irqen);
+}
+
+static inline void omap_mcspi_dmarequest_update(struct omap_mcspi_ch_s *ch)
+{
+    qemu_set_irq(ch->txdrq,
+                    (ch->control & 1) &&		/* EN */
+                    (ch->config & (1 << 14)) &&		/* DMAW */
+                    (ch->status & (1 << 1)) &&		/* TXS */
+                    ((ch->config >> 12) & 3) != 1);	/* TRM */
+    qemu_set_irq(ch->rxdrq,
+                    (ch->control & 1) &&		/* EN */
+                    (ch->config & (1 << 15)) &&		/* DMAW */
+                    (ch->status & (1 << 0)) &&		/* RXS */
+                    ((ch->config >> 12) & 3) != 2);	/* TRM */
+}
+
+static void omap_mcspi_transfer_run(struct omap_mcspi_s *s, int chnum)
+{
+    struct omap_mcspi_ch_s *ch = s->ch + chnum;
+
+    if (!(ch->control & 1))				/* EN */
+        return;
+    if ((ch->status & (1 << 0)) &&			/* RXS */
+                    ((ch->config >> 12) & 3) != 2 &&	/* TRM */
+                    !(ch->config & (1 << 19)))		/* TURBO */
+        goto intr_update;
+    if ((ch->status & (1 << 1)) &&			/* TXS */
+                    ((ch->config >> 12) & 3) != 1)	/* TRM */
+        goto intr_update;
+
+    if (!(s->control & 1) ||				/* SINGLE */
+                    (ch->config & (1 << 20))) {		/* FORCE */
+        if (ch->txrx)
+            ch->rx = ch->txrx(ch->opaque, ch->tx);
+    }
+
+    ch->tx = 0;
+    ch->status |= 1 << 2;				/* EOT */
+    ch->status |= 1 << 1;				/* TXS */
+    if (((ch->config >> 12) & 3) != 2)			/* TRM */
+        ch->status |= 1 << 0;				/* RXS */
+
+intr_update:
+    if ((ch->status & (1 << 0)) &&			/* RXS */
+                    ((ch->config >> 12) & 3) != 2 &&	/* TRM */
+                    !(ch->config & (1 << 19)))		/* TURBO */
+        s->irqst |= 1 << (2 + 4 * chnum);		/* RX_FULL */
+    if ((ch->status & (1 << 1)) &&			/* TXS */
+                    ((ch->config >> 12) & 3) != 1)	/* TRM */
+        s->irqst |= 1 << (0 + 4 * chnum);		/* TX_EMPTY */
+    omap_mcspi_interrupt_update(s);
+    omap_mcspi_dmarequest_update(ch);
+}
+
+static void omap_mcspi_reset(struct omap_mcspi_s *s)
+{
+    int ch;
+
+    s->sysconfig = 0;
+    s->systest = 0;
+    s->irqst = 0;
+    s->irqen = 0;
+    s->wken = 0;
+    s->control = 4;
+
+    for (ch = 0; ch < 4; ch ++) {
+        s->ch[ch].config = 0x060000;
+        s->ch[ch].status = 2;				/* TXS */
+        s->ch[ch].control = 0;
+
+        omap_mcspi_dmarequest_update(s->ch + ch);
+    }
+
+    omap_mcspi_interrupt_update(s);
+}
+
+static uint32_t omap_mcspi_read(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_mcspi_s *s = (struct omap_mcspi_s *) opaque;
+    int offset = addr - s->base;
+    int ch = 0;
+    uint32_t ret;
+
+    switch (offset) {
+    case 0x00:	/* MCSPI_REVISION */
+        return 0x91;
+
+    case 0x10:	/* MCSPI_SYSCONFIG */
+        return s->sysconfig;
+
+    case 0x14:	/* MCSPI_SYSSTATUS */
+        return 1;					/* RESETDONE */
+
+    case 0x18:	/* MCSPI_IRQSTATUS */
+        return s->irqst;
+
+    case 0x1c:	/* MCSPI_IRQENABLE */
+        return s->irqen;
+
+    case 0x20:	/* MCSPI_WAKEUPENABLE */
+        return s->wken;
+
+    case 0x24:	/* MCSPI_SYST */
+        return s->systest;
+
+    case 0x28:	/* MCSPI_MODULCTRL */
+        return s->control;
+
+    case 0x68: ch ++;
+    case 0x54: ch ++;
+    case 0x40: ch ++;
+    case 0x2c:	/* MCSPI_CHCONF */
+        return s->ch[ch].config;
+
+    case 0x6c: ch ++;
+    case 0x58: ch ++;
+    case 0x44: ch ++;
+    case 0x30:	/* MCSPI_CHSTAT */
+        return s->ch[ch].status;
+
+    case 0x70: ch ++;
+    case 0x5c: ch ++;
+    case 0x48: ch ++;
+    case 0x34:	/* MCSPI_CHCTRL */
+        return s->ch[ch].control;
+
+    case 0x74: ch ++;
+    case 0x60: ch ++;
+    case 0x4c: ch ++;
+    case 0x38:	/* MCSPI_TX */
+        return s->ch[ch].tx;
+
+    case 0x78: ch ++;
+    case 0x64: ch ++;
+    case 0x50: ch ++;
+    case 0x3c:	/* MCSPI_RX */
+        s->ch[ch].status &= ~(1 << 0);			/* RXS */
+        ret = s->ch[ch].rx;
+        omap_mcspi_transfer_run(s, ch);
+        return ret;
+    }
+
+    OMAP_BAD_REG(addr);
+    return 0;
+}
+
+static void omap_mcspi_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct omap_mcspi_s *s = (struct omap_mcspi_s *) opaque;
+    int offset = addr - s->base;
+    int ch = 0;
+
+    switch (offset) {
+    case 0x00:	/* MCSPI_REVISION */
+    case 0x14:	/* MCSPI_SYSSTATUS */
+    case 0x30:	/* MCSPI_CHSTAT0 */
+    case 0x3c:	/* MCSPI_RX0 */
+    case 0x44:	/* MCSPI_CHSTAT1 */
+    case 0x50:	/* MCSPI_RX1 */
+    case 0x58:	/* MCSPI_CHSTAT2 */
+    case 0x64:	/* MCSPI_RX2 */
+    case 0x6c:	/* MCSPI_CHSTAT3 */
+    case 0x78:	/* MCSPI_RX3 */
+        OMAP_RO_REG(addr);
+        return;
+
+    case 0x10:	/* MCSPI_SYSCONFIG */
+        if (value & (1 << 1))				/* SOFTRESET */
+            omap_mcspi_reset(s);
+        s->sysconfig = value & 0x31d;
+        break;
+
+    case 0x18:	/* MCSPI_IRQSTATUS */
+        if (!((s->control & (1 << 3)) && (s->systest & (1 << 11)))) {
+            s->irqst &= ~value;
+            omap_mcspi_interrupt_update(s);
+        }
+        break;
+
+    case 0x1c:	/* MCSPI_IRQENABLE */
+        s->irqen = value & 0x1777f;
+        omap_mcspi_interrupt_update(s);
+        break;
+
+    case 0x20:	/* MCSPI_WAKEUPENABLE */
+        s->wken = value & 1;
+        break;
+
+    case 0x24:	/* MCSPI_SYST */
+        if (s->control & (1 << 3))			/* SYSTEM_TEST */
+            if (value & (1 << 11)) {			/* SSB */
+                s->irqst |= 0x1777f;
+                omap_mcspi_interrupt_update(s);
+            }
+        s->systest = value & 0xfff;
+        break;
+
+    case 0x28:	/* MCSPI_MODULCTRL */
+        if (value & (1 << 3))				/* SYSTEM_TEST */
+            if (s->systest & (1 << 11)) {		/* SSB */
+                s->irqst |= 0x1777f;
+                omap_mcspi_interrupt_update(s);
+            }
+        s->control = value & 0xf;
+        break;
+
+    case 0x68: ch ++;
+    case 0x54: ch ++;
+    case 0x40: ch ++;
+    case 0x2c:	/* MCSPI_CHCONF */
+        if ((value ^ s->ch[ch].config) & (3 << 14))	/* DMAR | DMAW */
+            omap_mcspi_dmarequest_update(s->ch + ch);
+        if (((value >> 12) & 3) == 3)			/* TRM */
+            fprintf(stderr, "%s: invalid TRM value (3)\n", __FUNCTION__);
+        if (((value >> 7) & 0x1f) < 3)			/* WL */
+            fprintf(stderr, "%s: invalid WL value (%i)\n",
+                            __FUNCTION__, (value >> 7) & 0x1f);
+        s->ch[ch].config = value & 0x7fffff;
+        break;
+
+    case 0x70: ch ++;
+    case 0x5c: ch ++;
+    case 0x48: ch ++;
+    case 0x34:	/* MCSPI_CHCTRL */
+        if (value & ~s->ch[ch].control & 1) {		/* EN */
+            s->ch[ch].control |= 1;
+            omap_mcspi_transfer_run(s, ch);
+        } else
+            s->ch[ch].control = value & 1;
+        break;
+
+    case 0x74: ch ++;
+    case 0x60: ch ++;
+    case 0x4c: ch ++;
+    case 0x38:	/* MCSPI_TX */
+        s->ch[ch].tx = value;
+        s->ch[ch].status &= ~(1 << 1);			/* TXS */
+        omap_mcspi_transfer_run(s, ch);
+        break;
+
+    default:
+        OMAP_BAD_REG(addr);
+        return;
+    }
+}
+
+static CPUReadMemoryFunc *omap_mcspi_readfn[] = {
+    omap_badwidth_read32,
+    omap_badwidth_read32,
+    omap_mcspi_read,
+};
+
+static CPUWriteMemoryFunc *omap_mcspi_writefn[] = {
+    omap_badwidth_write32,
+    omap_badwidth_write32,
+    omap_mcspi_write,
+};
+
+struct omap_mcspi_s *omap_mcspi_init(struct omap_target_agent_s *ta, int chnum,
+                qemu_irq irq, qemu_irq *drq, omap_clk fclk, omap_clk iclk)
+{
+    int iomemtype;
+    struct omap_mcspi_s *s = (struct omap_mcspi_s *)
+            qemu_mallocz(sizeof(struct omap_mcspi_s));
+    struct omap_mcspi_ch_s *ch = s->ch;
+
+    s->irq = irq;
+    s->chnum = chnum;
+    while (chnum --) {
+        ch->txdrq = *drq ++;
+        ch->rxdrq = *drq ++;
+        ch ++;
+    }
+    omap_mcspi_reset(s);
+
+    iomemtype = cpu_register_io_memory(0, omap_mcspi_readfn,
+                    omap_mcspi_writefn, s);
+    s->base = omap_l4_attach(ta, 0, iomemtype);
+
+    return s;
+}
+
+void omap_mcspi_attach(struct omap_mcspi_s *s,
+                uint32_t (*txrx)(void *opaque, uint32_t), void *opaque,
+                int chipselect)
+{
+    if (chipselect < 0 || chipselect >= s->chnum)
+        cpu_abort(cpu_single_env, "%s: Bad chipselect %i\n",
+                        __FUNCTION__, chipselect);
+
+    s->ch[chipselect].txrx = txrx;
+    s->ch[chipselect].opaque = opaque;
+}
+
+/* L4 Interconnect */
+struct omap_target_agent_s {
+    struct omap_l4_s *bus;
+    int regions;
+    struct omap_l4_region_s *start;
+    target_phys_addr_t base;
+    uint32_t component;
+    uint32_t control;
+    uint32_t status;
+};
+
+struct omap_l4_s {
+    target_phys_addr_t base;
+    int ta_num;
+    struct omap_target_agent_s ta[0];
+};
+
+struct omap_l4_s *omap_l4_init(target_phys_addr_t base, int ta_num)
+{
+    struct omap_l4_s *bus = qemu_mallocz(
+                    sizeof(*bus) + ta_num * sizeof(*bus->ta));
+
+    bus->ta_num = ta_num;
+    bus->base = base;
+
+    return bus;
+}
+
+static uint32_t omap_l4ta_read(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_target_agent_s *s = (struct omap_target_agent_s *) opaque;
+    target_phys_addr_t reg = addr - s->base;
+
+    switch (reg) {
+    case 0x00:	/* COMPONENT */
+        return s->component;
+
+    case 0x20:	/* AGENT_CONTROL */
+        return s->control;
+
+    case 0x28:	/* AGENT_STATUS */
+        return s->status;
+    }
+
+    OMAP_BAD_REG(addr);
+    return 0;
+}
+
+static void omap_l4ta_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct omap_target_agent_s *s = (struct omap_target_agent_s *) opaque;
+    target_phys_addr_t reg = addr - s->base;
+
+    switch (reg) {
+    case 0x00:	/* COMPONENT */
+    case 0x28:	/* AGENT_STATUS */
+        OMAP_RO_REG(addr);
+        break;
+
+    case 0x20:	/* AGENT_CONTROL */
+        s->control = value & 0x01000700;
+        if (value & 1)					/* OCP_RESET */
+            s->status &= ~1;				/* REQ_TIMEOUT */
+        break;
+
+    default:
+        OMAP_BAD_REG(addr);
+    }
+}
+
+static CPUReadMemoryFunc *omap_l4ta_readfn[] = {
+    omap_badwidth_read16,
+    omap_l4ta_read,
+    omap_badwidth_read16,
+};
+
+static CPUWriteMemoryFunc *omap_l4ta_writefn[] = {
+    omap_badwidth_write32,
+    omap_badwidth_write32,
+    omap_l4ta_write,
+};
+
+#define L4TA(n)		(n)
+#define L4TAO(n)	((n) + 39)
+
+static struct omap_l4_region_s {
+    target_phys_addr_t offset;
+    size_t size;
+    int access;
+} 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 struct omap_l4_agent_info_s {
+    int ta;
+    int region;
+    int regions;
+    int ta_region;
+} 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, L4TA(cs))
+#define omap_l4tao(bus, cs)	omap_l4ta_get(bus, L4TAO(cs))
+
+struct omap_target_agent_s *omap_l4ta_get(struct omap_l4_s *bus, int cs)
+{
+    int i, iomemtype;
+    struct omap_target_agent_s *ta = 0;
+    struct omap_l4_agent_info_s *info = 0;
+
+    for (i = 0; i < bus->ta_num; i ++)
+        if (omap_l4_agent_info[i].ta == cs) {
+            ta = &bus->ta[i];
+            info = &omap_l4_agent_info[i];
+            break;
+        }
+    if (!ta) {
+        fprintf(stderr, "%s: bad target agent (%i)\n", __FUNCTION__, cs);
+        exit(-1);
+    }
+
+    ta->bus = bus;
+    ta->start = &omap_l4_region[info->region];
+    ta->regions = info->regions;
+    ta->base = bus->base + ta->start[info->ta_region].offset;
+
+    ta->component = ('Q' << 24) | ('E' << 16) | ('M' << 8) | ('U' << 0);
+    ta->status = 0x00000000;
+    ta->control = 0x00000200;	/* XXX 01000200 for L4TAO */
+
+    iomemtype = cpu_register_io_memory(0, omap_l4ta_readfn,
+                    omap_l4ta_writefn, ta);
+    cpu_register_physical_memory(ta->base, 0x200, iomemtype);
+
+    return ta;
+}
+
+target_phys_addr_t omap_l4_attach(struct omap_target_agent_s *ta, int region,
+                int iotype)
+{
+    target_phys_addr_t base;
+    size_t size;
+
+    if (region < 0 || region >= ta->regions) {
+        fprintf(stderr, "%s: bad io region (%i)\n", __FUNCTION__, region);
+        exit(-1);
+    }
+
+    base = ta->bus->base + ta->start[region].offset;
+    size = ta->start[region].size;
+    if (iotype)
+        cpu_register_physical_memory(base, size, iotype);
+
+    return base;
+}
+
+/* TEST-Chip-level TAP */
+static uint32_t omap_tap_read(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque;
+    target_phys_addr_t reg = addr - s->tap_base;
+
+    switch (reg) {
+    case 0x204:	/* IDCODE_reg */
+        switch (s->mpu_model) {
+        case omap2420:
+        case omap2422:
+        case omap2423:
+            return 0x5b5d902f;	/* ES 2.2 */
+        case omap2430:
+            return 0x5b68a02f;	/* ES 2.2 */
+        case omap3430:
+            return 0x1b7ae02f;	/* ES 2 */
+        default:
+            cpu_abort(cpu_single_env, "%s: Bad mpu model\n", __FUNCTION__);
+        }
+
+    case 0x208:	/* PRODUCTION_ID_reg for OMAP2 */
+    case 0x210:	/* PRODUCTION_ID_reg for OMAP3 */
+        switch (s->mpu_model) {
+        case omap2420:
+            return 0x000200f0;	/* POP ESHS2.1.1 in N91/93/95, ES2 in N800 */
+        case omap2422:
+            return 0x000400f0;
+        case omap2423:
+            return 0x000800f0;
+        case omap2430:
+            return 0x000000f0;
+        case omap3430:
+            return 0x000000f0;
+        default:
+            cpu_abort(cpu_single_env, "%s: Bad mpu model\n", __FUNCTION__);
+        }
+
+    case 0x218:	/* DIE_ID_reg */
+        return ('Q' << 24) | ('E' << 16) | ('M' << 8) | ('U' << 0);
+    case 0x21c:	/* DIE_ID_reg */
+        return (  5 << 24) | ('E' << 16) | ('M' << 8) | ('U' << 0);
+    case 0x220:	/* DIE_ID_reg */
+        return ('Q' << 24) | ('E' << 16) | ('M' << 8) | ('U' << 0);
+    case 0x224:	/* DIE_ID_reg */
+        return ('Q' << 24) | ('E' << 16) | ('M' << 8) | ('U' << 0);
+    }
+
+    OMAP_BAD_REG(addr);
+    return 0;
+}
+
+static void omap_tap_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    OMAP_BAD_REG(addr);
+}
+
+static CPUReadMemoryFunc *omap_tap_readfn[] = {
+    omap_badwidth_read32,
+    omap_badwidth_read32,
+    omap_tap_read,
+};
+
+static CPUWriteMemoryFunc *omap_tap_writefn[] = {
+    omap_badwidth_write32,
+    omap_badwidth_write32,
+    omap_tap_write,
+};
+
+void omap_tap_init(struct omap_target_agent_s *ta,
+                struct omap_mpu_state_s *mpu)
+{
+    mpu->tap_base = omap_l4_attach(ta, 0, cpu_register_io_memory(0,
+                            omap_tap_readfn, omap_tap_writefn, mpu));
+}
+
+/* Power, Reset, and Clock Management */
+struct omap_prcm_s {
+    target_phys_addr_t base;
+    qemu_irq irq[3];
+    struct omap_mpu_state_s *mpu;
+
+    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];
+};
+
+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 uint32_t omap_prcm_read(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_prcm_s *s = (struct omap_prcm_s *) opaque;
+    int offset = addr - s->base;
+
+    switch (offset) {
+    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[(offset - 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 */
+        /* Core uses 32-kHz clock */
+        if (!(s->clksel[6] & 3))
+            return 0x00000377;
+        /* DPLL not in lock mode, core uses ref_clk */
+        if ((s->clken[9] & 3) != 3)
+            return 0x00000375;
+        /* Core uses DPLL */
+        return 0x00000376;
+    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_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct omap_prcm_s *s = (struct omap_prcm_s *) opaque;
+    int offset = addr - s->base;
+
+    switch (offset) {
+    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[(offset - 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 */
+        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 */
+        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 */
+        s->clken[9] = value & 0xcf;
+        /* TODO update clocks */
+        break;
+    case 0x530:	/* CM_AUTOIDLE_PLL */
+        s->clkidle[5] = value & 0x000000cf;
+        /* TODO update clocks */
+        break;
+    case 0x540:	/* CM_CLKSEL1_PLL */
+        s->clksel[5] = value & 0x03bfff28;
+        /* TODO update clocks */
+        break;
+    case 0x544:	/* CM_CLKSEL2_PLL */
+        s->clksel[6] = value & 3;
+        /* TODO update clocks */
+        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 CPUReadMemoryFunc *omap_prcm_readfn[] = {
+    omap_badwidth_read32,
+    omap_badwidth_read32,
+    omap_prcm_read,
+};
+
+static CPUWriteMemoryFunc *omap_prcm_writefn[] = {
+    omap_badwidth_write32,
+    omap_badwidth_write32,
+    omap_prcm_write,
+};
+
+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;
+}
+
+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);
+}
+
+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)
+{
+    int iomemtype;
+    struct omap_prcm_s *s = (struct omap_prcm_s *)
+            qemu_mallocz(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);
+
+    iomemtype = cpu_register_io_memory(0, omap_prcm_readfn,
+                    omap_prcm_writefn, s);
+    s->base = omap_l4_attach(ta, 0, iomemtype);
+    omap_l4_attach(ta, 1, iomemtype);
+
+    return s;
+}
+
+/* System and Pinout control */
+struct omap_sysctl_s {
+    target_phys_addr_t base;
+    struct omap_mpu_state_s *mpu;
+
+    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_read(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_sysctl_s *s = (struct omap_sysctl_s *) opaque;
+    int offset = addr - s->base;
+
+    switch (offset) {
+    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[(offset - 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_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct omap_sysctl_s *s = (struct omap_sysctl_s *) opaque;
+    int offset = addr - s->base;
+
+    switch (offset) {
+    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[(offset - 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 CPUReadMemoryFunc *omap_sysctl_readfn[] = {
+    omap_badwidth_read32,	/* TODO */
+    omap_badwidth_read32,	/* TODO */
+    omap_sysctl_read,
+};
+
+static CPUWriteMemoryFunc *omap_sysctl_writefn[] = {
+    omap_badwidth_write32,	/* TODO */
+    omap_badwidth_write32,	/* TODO */
+    omap_sysctl_write,
+};
+
+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;
+}
+
+struct omap_sysctl_s *omap_sysctl_init(struct omap_target_agent_s *ta,
+                omap_clk iclk, struct omap_mpu_state_s *mpu)
+{
+    int iomemtype;
+    struct omap_sysctl_s *s = (struct omap_sysctl_s *)
+            qemu_mallocz(sizeof(struct omap_sysctl_s));
+
+    s->mpu = mpu;
+    omap_sysctl_reset(s);
+
+    iomemtype = cpu_register_io_memory(0, omap_sysctl_readfn,
+                    omap_sysctl_writefn, s);
+    s->base = omap_l4_attach(ta, 0, iomemtype);
+    omap_l4_attach(ta, 0, iomemtype);
+
+    return s;
+}
+
+/* SDRAM Controller Subsystem */
+struct omap_sdrc_s {
+    target_phys_addr_t base;
+
+    uint8_t config;
+};
+
+static void omap_sdrc_reset(struct omap_sdrc_s *s)
+{
+    s->config = 0x10;
+}
+
+static uint32_t omap_sdrc_read(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_sdrc_s *s = (struct omap_sdrc_s *) opaque;
+    int offset = addr - s->base;
+
+    switch (offset) {
+    case 0x00:	/* SDRC_REVISION */
+        return 0x20;
+
+    case 0x10:	/* SDRC_SYSCONFIG */
+        return s->config;
+
+    case 0x14:	/* SDRC_SYSSTATUS */
+        return 1;						/* RESETDONE */
+
+    case 0x40:	/* SDRC_CS_CFG */
+    case 0x44:	/* SDRC_SHARING */
+    case 0x48:	/* SDRC_ERR_ADDR */
+    case 0x4c:	/* SDRC_ERR_TYPE */
+    case 0x60:	/* SDRC_DLLA_SCTRL */
+    case 0x64:	/* SDRC_DLLA_STATUS */
+    case 0x68:	/* SDRC_DLLB_CTRL */
+    case 0x6c:	/* SDRC_DLLB_STATUS */
+    case 0x70:	/* SDRC_POWER */
+    case 0x80:	/* SDRC_MCFG_0 */
+    case 0x84:	/* SDRC_MR_0 */
+    case 0x88:	/* SDRC_EMR1_0 */
+    case 0x8c:	/* SDRC_EMR2_0 */
+    case 0x90:	/* SDRC_EMR3_0 */
+    case 0x94:	/* SDRC_DCDL1_CTRL */
+    case 0x98:	/* SDRC_DCDL2_CTRL */
+    case 0x9c:	/* SDRC_ACTIM_CTRLA_0 */
+    case 0xa0:	/* SDRC_ACTIM_CTRLB_0 */
+    case 0xa4:	/* SDRC_RFR_CTRL_0 */
+    case 0xa8:	/* SDRC_MANUAL_0 */
+    case 0xb0:	/* SDRC_MCFG_1 */
+    case 0xb4:	/* SDRC_MR_1 */
+    case 0xb8:	/* SDRC_EMR1_1 */
+    case 0xbc:	/* SDRC_EMR2_1 */
+    case 0xc0:	/* SDRC_EMR3_1 */
+    case 0xc4:	/* SDRC_ACTIM_CTRLA_1 */
+    case 0xc8:	/* SDRC_ACTIM_CTRLB_1 */
+    case 0xd4:	/* SDRC_RFR_CTRL_1 */
+    case 0xd8:	/* SDRC_MANUAL_1 */
+        return 0x00;
+    }
+
+    OMAP_BAD_REG(addr);
+    return 0;
+}
+
+static void omap_sdrc_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct omap_sdrc_s *s = (struct omap_sdrc_s *) opaque;
+    int offset = addr - s->base;
+
+    switch (offset) {
+    case 0x00:	/* SDRC_REVISION */
+    case 0x14:	/* SDRC_SYSSTATUS */
+    case 0x48:	/* SDRC_ERR_ADDR */
+    case 0x64:	/* SDRC_DLLA_STATUS */
+    case 0x6c:	/* SDRC_DLLB_STATUS */
+        OMAP_RO_REG(addr);
+        return;
+
+    case 0x10:	/* SDRC_SYSCONFIG */
+        if ((value >> 3) != 0x2)
+            fprintf(stderr, "%s: bad SDRAM idle mode %i\n",
+                            __FUNCTION__, value >> 3);
+        if (value & 2)
+            omap_sdrc_reset(s);
+        s->config = value & 0x18;
+        break;
+
+    case 0x40:	/* SDRC_CS_CFG */
+    case 0x44:	/* SDRC_SHARING */
+    case 0x4c:	/* SDRC_ERR_TYPE */
+    case 0x60:	/* SDRC_DLLA_SCTRL */
+    case 0x68:	/* SDRC_DLLB_CTRL */
+    case 0x70:	/* SDRC_POWER */
+    case 0x80:	/* SDRC_MCFG_0 */
+    case 0x84:	/* SDRC_MR_0 */
+    case 0x88:	/* SDRC_EMR1_0 */
+    case 0x8c:	/* SDRC_EMR2_0 */
+    case 0x90:	/* SDRC_EMR3_0 */
+    case 0x94:	/* SDRC_DCDL1_CTRL */
+    case 0x98:	/* SDRC_DCDL2_CTRL */
+    case 0x9c:	/* SDRC_ACTIM_CTRLA_0 */
+    case 0xa0:	/* SDRC_ACTIM_CTRLB_0 */
+    case 0xa4:	/* SDRC_RFR_CTRL_0 */
+    case 0xa8:	/* SDRC_MANUAL_0 */
+    case 0xb0:	/* SDRC_MCFG_1 */
+    case 0xb4:	/* SDRC_MR_1 */
+    case 0xb8:	/* SDRC_EMR1_1 */
+    case 0xbc:	/* SDRC_EMR2_1 */
+    case 0xc0:	/* SDRC_EMR3_1 */
+    case 0xc4:	/* SDRC_ACTIM_CTRLA_1 */
+    case 0xc8:	/* SDRC_ACTIM_CTRLB_1 */
+    case 0xd4:	/* SDRC_RFR_CTRL_1 */
+    case 0xd8:	/* SDRC_MANUAL_1 */
+        break;
+
+    default:
+        OMAP_BAD_REG(addr);
+        return;
+    }
+}
+
+static CPUReadMemoryFunc *omap_sdrc_readfn[] = {
+    omap_badwidth_read32,
+    omap_badwidth_read32,
+    omap_sdrc_read,
+};
+
+static CPUWriteMemoryFunc *omap_sdrc_writefn[] = {
+    omap_badwidth_write32,
+    omap_badwidth_write32,
+    omap_sdrc_write,
+};
+
+struct omap_sdrc_s *omap_sdrc_init(target_phys_addr_t base)
+{
+    int iomemtype;
+    struct omap_sdrc_s *s = (struct omap_sdrc_s *)
+            qemu_mallocz(sizeof(struct omap_sdrc_s));
+
+    s->base = base;
+    omap_sdrc_reset(s);
+
+    iomemtype = cpu_register_io_memory(0, omap_sdrc_readfn,
+                    omap_sdrc_writefn, s);
+    cpu_register_physical_memory(s->base, 0x1000, iomemtype);
+
+    return s;
+}
+
+/* General-Purpose Memory Controller */
+struct omap_gpmc_s {
+    target_phys_addr_t base;
+    qemu_irq irq;
+
+    uint8_t sysconfig;
+    uint16_t irqst;
+    uint16_t irqen;
+    uint16_t timeout;
+    uint16_t config;
+    uint32_t prefconfig[2];
+    int prefcontrol;
+    int preffifo;
+    int prefcount;
+    struct omap_gpmc_cs_file_s {
+        uint32_t config[7];
+        target_phys_addr_t base;
+        size_t size;
+        int iomemtype;
+        void (*base_update)(void *opaque, target_phys_addr_t new);
+        void (*unmap)(void *opaque);
+        void *opaque;
+    } cs_file[8];
+    int ecc_cs;
+    int ecc_ptr;
+    uint32_t ecc_cfg;
+    struct ecc_state_s ecc[9];
+};
+
+static void omap_gpmc_int_update(struct omap_gpmc_s *s)
+{
+    qemu_set_irq(s->irq, s->irqen & s->irqst);
+}
+
+static void omap_gpmc_cs_map(struct omap_gpmc_cs_file_s *f, int base, int mask)
+{
+    /* TODO: check for overlapping regions and report access errors */
+    if ((mask != 0x8 && mask != 0xc && mask != 0xe && mask != 0xf) ||
+                    (base < 0 || base >= 0x40) ||
+                    (base & 0x0f & ~mask)) {
+        fprintf(stderr, "%s: wrong cs address mapping/decoding!\n",
+                        __FUNCTION__);
+        return;
+    }
+
+    if (!f->opaque)
+        return;
+
+    f->base = base << 24;
+    f->size = (0x0fffffff & ~(mask << 24)) + 1;
+    /* TODO: rather than setting the size of the mapping (which should be
+     * constant), the mask should cause wrapping of the address space, so
+     * that the same memory becomes accessible at every <i>size</i> bytes
+     * starting from <i>base</i>.  */
+    if (f->iomemtype)
+        cpu_register_physical_memory(f->base, f->size, f->iomemtype);
+
+    if (f->base_update)
+        f->base_update(f->opaque, f->base);
+}
+
+static void omap_gpmc_cs_unmap(struct omap_gpmc_cs_file_s *f)
+{
+    if (f->size) {
+        if (f->unmap)
+            f->unmap(f->opaque);
+        if (f->iomemtype)
+            cpu_register_physical_memory(f->base, f->size, IO_MEM_UNASSIGNED);
+        f->base = 0;
+        f->size = 0;
+    }
+}
+
+static void omap_gpmc_reset(struct omap_gpmc_s *s)
+{
+    int i;
+
+    s->sysconfig = 0;
+    s->irqst = 0;
+    s->irqen = 0;
+    omap_gpmc_int_update(s);
+    s->timeout = 0;
+    s->config = 0xa00;
+    s->prefconfig[0] = 0x00004000;
+    s->prefconfig[1] = 0x00000000;
+    s->prefcontrol = 0;
+    s->preffifo = 0;
+    s->prefcount = 0;
+    for (i = 0; i < 8; i ++) {
+        if (s->cs_file[i].config[6] & (1 << 6))			/* CSVALID */
+            omap_gpmc_cs_unmap(s->cs_file + i);
+        s->cs_file[i].config[0] = i ? 1 << 12 : 0;
+        s->cs_file[i].config[1] = 0x101001;
+        s->cs_file[i].config[2] = 0x020201;
+        s->cs_file[i].config[3] = 0x10031003;
+        s->cs_file[i].config[4] = 0x10f1111;
+        s->cs_file[i].config[5] = 0;
+        s->cs_file[i].config[6] = 0xf00 | (i ? 0 : 1 << 6);
+        if (s->cs_file[i].config[6] & (1 << 6))			/* CSVALID */
+            omap_gpmc_cs_map(&s->cs_file[i],
+                            s->cs_file[i].config[6] & 0x1f,	/* MASKADDR */
+                        (s->cs_file[i].config[6] >> 8 & 0xf));	/* BASEADDR */
+    }
+    omap_gpmc_cs_map(s->cs_file, 0, 0xf);
+    s->ecc_cs = 0;
+    s->ecc_ptr = 0;
+    s->ecc_cfg = 0x3fcff000;
+    for (i = 0; i < 9; i ++)
+        ecc_reset(&s->ecc[i]);
+}
+
+static uint32_t omap_gpmc_read(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_gpmc_s *s = (struct omap_gpmc_s *) opaque;
+    int offset = addr - s->base;
+    int cs;
+    struct omap_gpmc_cs_file_s *f;
+
+    switch (offset) {
+    case 0x000:	/* GPMC_REVISION */
+        return 0x20;
+
+    case 0x010:	/* GPMC_SYSCONFIG */
+        return s->sysconfig;
+
+    case 0x014:	/* GPMC_SYSSTATUS */
+        return 1;						/* RESETDONE */
+
+    case 0x018:	/* GPMC_IRQSTATUS */
+        return s->irqst;
+
+    case 0x01c:	/* GPMC_IRQENABLE */
+        return s->irqen;
+
+    case 0x040:	/* GPMC_TIMEOUT_CONTROL */
+        return s->timeout;
+
+    case 0x044:	/* GPMC_ERR_ADDRESS */
+    case 0x048:	/* GPMC_ERR_TYPE */
+        return 0;
+
+    case 0x050:	/* GPMC_CONFIG */
+        return s->config;
+
+    case 0x054:	/* GPMC_STATUS */
+        return 0x001;
+
+    case 0x060 ... 0x1d4:
+        cs = (offset - 0x060) / 0x30;
+        offset -= cs * 0x30;
+        f = s->cs_file + cs;
+        switch (offset - cs * 0x30) {
+            case 0x60:	/* GPMC_CONFIG1 */
+                return f->config[0];
+            case 0x64:	/* GPMC_CONFIG2 */
+                return f->config[1];
+            case 0x68:	/* GPMC_CONFIG3 */
+                return f->config[2];
+            case 0x6c:	/* GPMC_CONFIG4 */
+                return f->config[3];
+            case 0x70:	/* GPMC_CONFIG5 */
+                return f->config[4];
+            case 0x74:	/* GPMC_CONFIG6 */
+                return f->config[5];
+            case 0x78:	/* GPMC_CONFIG7 */
+                return f->config[6];
+            case 0x84:	/* GPMC_NAND_DATA */
+                return 0;
+        }
+        break;
+
+    case 0x1e0:	/* GPMC_PREFETCH_CONFIG1 */
+        return s->prefconfig[0];
+    case 0x1e4:	/* GPMC_PREFETCH_CONFIG2 */
+        return s->prefconfig[1];
+    case 0x1ec:	/* GPMC_PREFETCH_CONTROL */
+        return s->prefcontrol;
+    case 0x1f0:	/* GPMC_PREFETCH_STATUS */
+        return (s->preffifo << 24) |
+                ((s->preffifo >
+                  ((s->prefconfig[0] >> 8) & 0x7f) ? 1 : 0) << 16) |
+                s->prefcount;
+
+    case 0x1f4:	/* GPMC_ECC_CONFIG */
+        return s->ecc_cs;
+    case 0x1f8:	/* GPMC_ECC_CONTROL */
+        return s->ecc_ptr;
+    case 0x1fc:	/* GPMC_ECC_SIZE_CONFIG */
+        return s->ecc_cfg;
+    case 0x200 ... 0x220:	/* GPMC_ECC_RESULT */
+        cs = (offset & 0x1f) >> 2;
+        /* TODO: check correctness */
+        return
+                ((s->ecc[cs].cp    &  0x07) <<  0) |
+                ((s->ecc[cs].cp    &  0x38) << 13) |
+                ((s->ecc[cs].lp[0] & 0x1ff) <<  3) |
+                ((s->ecc[cs].lp[1] & 0x1ff) << 19);
+
+    case 0x230:	/* GPMC_TESTMODE_CTRL */
+        return 0;
+    case 0x234:	/* GPMC_PSA_LSB */
+    case 0x238:	/* GPMC_PSA_MSB */
+        return 0x00000000;
+    }
+
+    OMAP_BAD_REG(addr);
+    return 0;
+}
+
+static void omap_gpmc_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct omap_gpmc_s *s = (struct omap_gpmc_s *) opaque;
+    int offset = addr - s->base;
+    int cs;
+    struct omap_gpmc_cs_file_s *f;
+
+    switch (offset) {
+    case 0x000:	/* GPMC_REVISION */
+    case 0x014:	/* GPMC_SYSSTATUS */
+    case 0x054:	/* GPMC_STATUS */
+    case 0x1f0:	/* GPMC_PREFETCH_STATUS */
+    case 0x200 ... 0x220:	/* GPMC_ECC_RESULT */
+    case 0x234:	/* GPMC_PSA_LSB */
+    case 0x238:	/* GPMC_PSA_MSB */
+        OMAP_RO_REG(addr);
+        break;
+
+    case 0x010:	/* GPMC_SYSCONFIG */
+        if ((value >> 3) == 0x3)
+            fprintf(stderr, "%s: bad SDRAM idle mode %i\n",
+                            __FUNCTION__, value >> 3);
+        if (value & 2)
+            omap_gpmc_reset(s);
+        s->sysconfig = value & 0x19;
+        break;
+
+    case 0x018:	/* GPMC_IRQSTATUS */
+        s->irqen = ~value;
+        omap_gpmc_int_update(s);
+        break;
+
+    case 0x01c:	/* GPMC_IRQENABLE */
+        s->irqen = value & 0xf03;
+        omap_gpmc_int_update(s);
+        break;
+
+    case 0x040:	/* GPMC_TIMEOUT_CONTROL */
+        s->timeout = value & 0x1ff1;
+        break;
+
+    case 0x044:	/* GPMC_ERR_ADDRESS */
+    case 0x048:	/* GPMC_ERR_TYPE */
+        break;
+
+    case 0x050:	/* GPMC_CONFIG */
+        s->config = value & 0xf13;
+        break;
+
+    case 0x060 ... 0x1d4:
+        cs = (offset - 0x060) / 0x30;
+        offset -= cs * 0x30;
+        f = s->cs_file + cs;
+        switch (offset - cs * 0x30) {
+            case 0x60:	/* GPMC_CONFIG1 */
+                f->config[0] = value & 0xffef3e13;
+                break;
+            case 0x64:	/* GPMC_CONFIG2 */
+                f->config[1] = value & 0x001f1f8f;
+                break;
+            case 0x68:	/* GPMC_CONFIG3 */
+                f->config[2] = value & 0x001f1f8f;
+                break;
+            case 0x6c:	/* GPMC_CONFIG4 */
+                f->config[3] = value & 0x1f8f1f8f;
+                break;
+            case 0x70:	/* GPMC_CONFIG5 */
+                f->config[4] = value & 0x0f1f1f1f;
+                break;
+            case 0x74:	/* GPMC_CONFIG6 */
+                f->config[5] = value & 0x00000fcf;
+                break;
+            case 0x78:	/* GPMC_CONFIG7 */
+                if ((f->config[6] ^ value) & 0xf7f) {
+                    if (f->config[6] & (1 << 6))		/* CSVALID */
+                        omap_gpmc_cs_unmap(f);
+                    if (value & (1 << 6))			/* CSVALID */
+                        omap_gpmc_cs_map(f, value & 0x1f,	/* MASKADDR */
+                                        (value >> 8 & 0xf));	/* BASEADDR */
+                }
+                f->config[6] = value & 0x00000f7f;
+                break;
+            case 0x7c:	/* GPMC_NAND_COMMAND */
+            case 0x80:	/* GPMC_NAND_ADDRESS */
+            case 0x84:	/* GPMC_NAND_DATA */
+                break;
+
+            default:
+                goto bad_reg;
+        }
+        break;
+
+    case 0x1e0:	/* GPMC_PREFETCH_CONFIG1 */
+        s->prefconfig[0] = value & 0x7f8f7fbf;
+        /* TODO: update interrupts, fifos, dmas */
+        break;
+
+    case 0x1e4:	/* GPMC_PREFETCH_CONFIG2 */
+        s->prefconfig[1] = value & 0x3fff;
+        break;
+
+    case 0x1ec:	/* GPMC_PREFETCH_CONTROL */
+        s->prefcontrol = value & 1;
+        if (s->prefcontrol) {
+            if (s->prefconfig[0] & 1)
+                s->preffifo = 0x40;
+            else
+                s->preffifo = 0x00;
+        }
+        /* TODO: start */
+        break;
+
+    case 0x1f4:	/* GPMC_ECC_CONFIG */
+        s->ecc_cs = 0x8f;
+        break;
+    case 0x1f8:	/* GPMC_ECC_CONTROL */
+        if (value & (1 << 8))
+            for (cs = 0; cs < 9; cs ++)
+                ecc_reset(&s->ecc[cs]);
+        s->ecc_ptr = value & 0xf;
+        if (s->ecc_ptr == 0 || s->ecc_ptr > 9) {
+            s->ecc_ptr = 0;
+            s->ecc_cs &= ~1;
+        }
+        break;
+    case 0x1fc:	/* GPMC_ECC_SIZE_CONFIG */
+        s->ecc_cfg = value & 0x3fcff1ff;
+        break;
+    case 0x230:	/* GPMC_TESTMODE_CTRL */
+        if (value & 7)
+            fprintf(stderr, "%s: test mode enable attempt\n", __FUNCTION__);
+        break;
+
+    default:
+    bad_reg:
+        OMAP_BAD_REG(addr);
+        return;
+    }
+}
+
+static CPUReadMemoryFunc *omap_gpmc_readfn[] = {
+    omap_badwidth_read32,	/* TODO */
+    omap_badwidth_read32,	/* TODO */
+    omap_gpmc_read,
+};
+
+static CPUWriteMemoryFunc *omap_gpmc_writefn[] = {
+    omap_badwidth_write32,	/* TODO */
+    omap_badwidth_write32,	/* TODO */
+    omap_gpmc_write,
+};
+
+struct omap_gpmc_s *omap_gpmc_init(target_phys_addr_t base, qemu_irq irq)
+{
+    int iomemtype;
+    struct omap_gpmc_s *s = (struct omap_gpmc_s *)
+            qemu_mallocz(sizeof(struct omap_gpmc_s));
+
+    s->base = base;
+    omap_gpmc_reset(s);
+
+    iomemtype = cpu_register_io_memory(0, omap_gpmc_readfn,
+                    omap_gpmc_writefn, s);
+    cpu_register_physical_memory(s->base, 0x1000, iomemtype);
+
+    return s;
+}
+
+void omap_gpmc_attach(struct omap_gpmc_s *s, int cs, int iomemtype,
+                void (*base_upd)(void *opaque, target_phys_addr_t new),
+                void (*unmap)(void *opaque), void *opaque)
+{
+    struct omap_gpmc_cs_file_s *f;
+
+    if (cs < 0 || cs >= 8) {
+        fprintf(stderr, "%s: bad chip-select %i\n", __FUNCTION__, cs);
+        exit(-1);
+    }
+    f = &s->cs_file[cs];
+
+    f->iomemtype = iomemtype;
+    f->base_update = base_upd;
+    f->unmap = unmap;
+    f->opaque = opaque;
+
+    if (f->config[6] & (1 << 6))				/* CSVALID */
+        omap_gpmc_cs_map(f, f->config[6] & 0x1f,		/* MASKADDR */
+                        (f->config[6] >> 8 & 0xf));		/* BASEADDR */
+}
+
+/* General chip reset */
+static void omap2_mpu_reset(void *opaque)
+{
+    struct omap_mpu_state_s *mpu = (struct omap_mpu_state_s *) opaque;
+
+    omap_inth_reset(mpu->ih[0]);
+    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);
+#if 0
+    omap_wd_timer_reset(mpu->wdt);
+    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]);
+#endif
+    omap_uart_reset(mpu->uart[0]);
+    omap_uart_reset(mpu->uart[1]);
+    omap_uart_reset(mpu->uart[2]);
+    omap_mmc_reset(mpu->mmc);
+    omap_gpif_reset(mpu->gpif);
+    omap_mcspi_reset(mpu->mcspi[0]);
+    omap_mcspi_reset(mpu->mcspi[1]);
+#if 0
+    omap_pwl_reset(mpu);
+    omap_pwt_reset(mpu);
+#endif
+    omap_i2c_reset(mpu->i2c[0]);
+    omap_i2c_reset(mpu->i2c[1]);
+#if 0
+    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);
+#endif
+    cpu_reset(mpu->env);
+}
+
+static int omap2_validate_addr(struct omap_mpu_state_s *s,
+                target_phys_addr_t 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(unsigned long sdram_size,
+                DisplayState *ds, const char *core)
+{
+    struct omap_mpu_state_s *s = (struct omap_mpu_state_s *)
+            qemu_mallocz(sizeof(struct omap_mpu_state_s));
+    ram_addr_t sram_base, q3_base;
+    qemu_irq *cpu_irq;
+    qemu_irq dma_irqs[4];
+    omap_clk gpio_clks[4];
+    int sdindex;
+    int i;
+
+    /* Core */
+    s->mpu_model = omap2420;
+    s->env = cpu_init(core ?: "arm1136-r2");
+    if (!s->env) {
+        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 */
+    cpu_register_physical_memory(OMAP2_Q2_BASE, s->sdram_size,
+                    (q3_base = qemu_ram_alloc(s->sdram_size)) | IO_MEM_RAM);
+    cpu_register_physical_memory(OMAP2_SRAM_BASE, s->sram_size,
+                    (sram_base = qemu_ram_alloc(s->sram_size)) | IO_MEM_RAM);
+
+    s->l4 = omap_l4_init(OMAP2_L4_BASE, 54);
+
+    /* Actually mapped at any 2K boundary in the ARM11 private-peripheral if */
+    cpu_irq = arm_pic_init_cpu(s->env);
+    s->ih[0] = omap2_inth_init(0x480fe000, 0x1000, 3, &s->irq[0],
+                    cpu_irq[ARM_PIC_CPU_IRQ], cpu_irq[ARM_PIC_CPU_FIQ],
+                    omap_findclk(s, "mpu_intc_fclk"),
+                    omap_findclk(s, "mpu_intc_iclk"));
+
+    s->prcm = omap_prcm_init(omap_l4tao(s->l4, 3),
+                    s->irq[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] =
+                s->irq[omap2_dma_irq_map[i].ih][omap2_dma_irq_map[i].intr];
+    s->dma = omap_dma4_init(0x48056000, dma_irqs, s, 256, 32,
+                    omap_findclk(s, "sdma_iclk"),
+                    omap_findclk(s, "sdma_fclk"));
+    s->port->addr_valid = omap2_validate_addr;
+
+    s->uart[0] = omap2_uart_init(omap_l4ta(s->l4, 19),
+                    s->irq[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], serial_hds[0]);
+    s->uart[1] = omap2_uart_init(omap_l4ta(s->l4, 20),
+                    s->irq[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],
+                    serial_hds[0] ? serial_hds[1] : 0);
+    s->uart[2] = omap2_uart_init(omap_l4ta(s->l4, 21),
+                    s->irq[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],
+                    serial_hds[0] && serial_hds[1] ? serial_hds[2] : 0);
+
+    s->gptimer[0] = omap_gp_timer_init(omap_l4ta(s->l4, 7),
+                    s->irq[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),
+                    s->irq[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),
+                    s->irq[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),
+                    s->irq[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),
+                    s->irq[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),
+                    s->irq[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),
+                    s->irq[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),
+                    s->irq[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),
+                    s->irq[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),
+                    s->irq[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),
+                    s->irq[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),
+                    s->irq[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);
+
+    omap_synctimer_init(omap_l4tao(s->l4, 2), s,
+                    omap_findclk(s, "clk32-kHz"),
+                    omap_findclk(s, "core_l4_iclk"));
+
+    s->i2c[0] = omap2_i2c_init(omap_l4tao(s->l4, 5),
+                    s->irq[0][OMAP_INT_24XX_I2C1_IRQ],
+                    &s->drq[OMAP24XX_DMA_I2C1_TX],
+                    omap_findclk(s, "i2c1.fclk"),
+                    omap_findclk(s, "i2c1.iclk"));
+    s->i2c[1] = omap2_i2c_init(omap_l4tao(s->l4, 6),
+                    s->irq[0][OMAP_INT_24XX_I2C2_IRQ],
+                    &s->drq[OMAP24XX_DMA_I2C2_TX],
+                    omap_findclk(s, "i2c2.fclk"),
+                    omap_findclk(s, "i2c2.iclk"));
+
+    gpio_clks[0] = omap_findclk(s, "gpio1_dbclk");
+    gpio_clks[1] = omap_findclk(s, "gpio2_dbclk");
+    gpio_clks[2] = omap_findclk(s, "gpio3_dbclk");
+    gpio_clks[3] = omap_findclk(s, "gpio4_dbclk");
+    s->gpif = omap2_gpio_init(omap_l4ta(s->l4, 3),
+                    &s->irq[0][OMAP_INT_24XX_GPIO_BANK1],
+                    gpio_clks, omap_findclk(s, "gpio_iclk"), 4);
+
+    s->sdrc = omap_sdrc_init(0x68009000);
+    s->gpmc = omap_gpmc_init(0x6800a000, s->irq[0][OMAP_INT_24XX_GPMC_IRQ]);
+
+    sdindex = drive_get_index(IF_SD, 0, 0);
+    if (sdindex == -1) {
+        fprintf(stderr, "qemu: missing SecureDigital device\n");
+        exit(1);
+    }
+    s->mmc = omap2_mmc_init(omap_l4tao(s->l4, 9), drives_table[sdindex].bdrv,
+                    s->irq[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,
+                    s->irq[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,
+                    s->irq[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), 0x68000800, ds,
+                    /* XXX wire M_IRQ_25, D_L2_IRQ_30 and I_IRQ_13 together */
+                    s->irq[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"));
+
+    /* Register mappings 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
+     * 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/omap_clk.c b/hw/omap_clk.c
index 37daec2..da03e15 100644
--- a/hw/omap_clk.c
+++ b/hw/omap_clk.c
@@ -34,6 +34,9 @@ struct clk {
 #define CLOCK_IN_OMAP730	(1 << 11)
 #define CLOCK_IN_OMAP1510	(1 << 12)
 #define CLOCK_IN_OMAP16XX	(1 << 13)
+#define CLOCK_IN_OMAP242X	(1 << 14)
+#define CLOCK_IN_OMAP243X	(1 << 15)
+#define CLOCK_IN_OMAP343X	(1 << 16)
     uint32_t flags;
     int id;
 
@@ -55,7 +58,8 @@ static struct clk xtal_osc12m = {
 static struct clk xtal_osc32k = {
     .name	= "xtal_osc_32k",
     .rate	= 32768,
-    .flags	= CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310,
+    .flags	= CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX | CLOCK_IN_OMAP310 |
+            CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
 };
 
 static struct clk ck_ref = {
@@ -502,11 +506,441 @@ static struct clk i2c_ick = {
 static struct clk clk32k = {
     .name	= "clk32-kHz",
     .flags	= CLOCK_IN_OMAP310 | CLOCK_IN_OMAP1510 | CLOCK_IN_OMAP16XX |
-            ALWAYS_ENABLED,
-    .parent     = &xtal_osc32k,
+            CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+    .parent	= &xtal_osc32k,
+};
+
+static struct clk apll_96m = {
+    .name	= "apll_96m",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+    .rate	= 96000000,
+    /*.parent	= sys.xtalin */
+};
+
+static struct clk apll_54m = {
+    .name	= "apll_54m",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+    .rate	= 54000000,
+    /*.parent	= sys.xtalin */
+};
+
+static struct clk sys_clk = {
+    .name	= "sys_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+    .rate	= 32768,
+    /*.parent	= sys.xtalin */
+};
+
+static struct clk sleep_clk = {
+    .name	= "sleep_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+    .rate	= 32768,
+    /*.parent	= sys.xtalin */
+};
+
+static struct clk dpll_ck = {
+    .name	= "dpll",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+    /*.parent	= sys.xtalin */
+};
+
+static struct clk dpll_x2_ck = {
+    .name	= "dpll_x2",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+    /*.parent	= sys.xtalin */
+};
+
+static struct clk wdt1_sys_clk = {
+    .name	= "wdt1_sys_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X | ALWAYS_ENABLED,
+    .rate	= 32768,
+    /*.parent	= sys.xtalin */
+};
+
+static struct clk func_96m_clk = {
+    .name	= "func_96m_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .divisor	= 1,
+    .parent	= &apll_96m,
+};
+
+static struct clk func_48m_clk = {
+    .name	= "func_48m_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .divisor	= 2,
+    .parent	= &apll_96m,
+};
+
+static struct clk func_12m_clk = {
+    .name	= "func_12m_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .divisor	= 8,
+    .parent	= &apll_96m,
+};
+
+static struct clk func_54m_clk = {
+    .name	= "func_54m_clk",
+    .flags	= CLOCK_IN_OMAP242X,
+    .divisor	= 1,
+    .parent	= &apll_54m,
+};
+
+static struct clk sys_clkout = {
+    .name	= "clkout",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &sys_clk,
+};
+
+static struct clk sys_clkout2 = {
+    .name	= "clkout2",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &sys_clk,
+};
+
+static struct clk core_clk = {
+    .name	= "core_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &dpll_ck,
+};
+
+static struct clk l3_clk = {
+    .name	= "l3_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &core_clk,
+};
+
+static struct clk core_l4_iclk = {
+    .name	= "core_l4_iclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &l3_clk,
+};
+
+static struct clk wu_l4_iclk = {
+    .name	= "wu_l4_iclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &l3_clk,
+};
+
+static struct clk core_l3_iclk = {
+    .name	= "core_l3_iclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &core_clk,
+};
+
+static struct clk core_l4_usb_clk = {
+    .name	= "core_l4_usb_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &l3_clk,
+};
+
+static struct clk wu_gpt1_clk = {
+    .name	= "wu_gpt1_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &sys_clk,
+};
+
+static struct clk wu_32k_clk = {
+    .name	= "wu_32k_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &sys_clk,
+};
+
+static struct clk uart1_fclk = {
+    .name	= "uart1_fclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &func_48m_clk,
+};
+
+static struct clk uart1_iclk = {
+    .name	= "uart1_iclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &core_l4_iclk,
+};
+
+static struct clk uart2_fclk = {
+    .name	= "uart2_fclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &func_48m_clk,
+};
+
+static struct clk uart2_iclk = {
+    .name	= "uart2_iclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &core_l4_iclk,
+};
+
+static struct clk uart3_fclk = {
+    .name	= "uart3_fclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &func_48m_clk,
+};
+
+static struct clk uart3_iclk = {
+    .name	= "uart3_iclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &core_l4_iclk,
+};
+
+static struct clk mpu_fclk = {
+    .name	= "mpu_fclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &core_clk,
+};
+
+static struct clk mpu_iclk = {
+    .name	= "mpu_iclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &core_clk,
+};
+
+static struct clk int_m_fclk = {
+    .name	= "int_m_fclk",
+    .alias	= "mpu_intc_fclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &core_clk,
+};
+
+static struct clk int_m_iclk = {
+    .name	= "int_m_iclk",
+    .alias	= "mpu_intc_iclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &core_clk,
+};
+
+static struct clk core_gpt2_clk = {
+    .name	= "core_gpt2_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &sys_clk,
+};
+
+static struct clk core_gpt3_clk = {
+    .name	= "core_gpt3_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &sys_clk,
+};
+
+static struct clk core_gpt4_clk = {
+    .name	= "core_gpt4_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &sys_clk,
+};
+
+static struct clk core_gpt5_clk = {
+    .name	= "core_gpt5_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &sys_clk,
+};
+
+static struct clk core_gpt6_clk = {
+    .name	= "core_gpt6_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &sys_clk,
+};
+
+static struct clk core_gpt7_clk = {
+    .name	= "core_gpt7_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &sys_clk,
+};
+
+static struct clk core_gpt8_clk = {
+    .name	= "core_gpt8_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &sys_clk,
+};
+
+static struct clk core_gpt9_clk = {
+    .name	= "core_gpt9_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &sys_clk,
+};
+
+static struct clk core_gpt10_clk = {
+    .name	= "core_gpt10_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &sys_clk,
+};
+
+static struct clk core_gpt11_clk = {
+    .name	= "core_gpt11_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &sys_clk,
+};
+
+static struct clk core_gpt12_clk = {
+    .name	= "core_gpt12_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &sys_clk,
+};
+
+static struct clk mcbsp1_clk = {
+    .name	= "mcbsp1_cg",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .divisor	= 2,
+    .parent	= &func_96m_clk,
+};
+
+static struct clk mcbsp2_clk = {
+    .name	= "mcbsp2_cg",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .divisor	= 2,
+    .parent	= &func_96m_clk,
+};
+
+static struct clk emul_clk = {
+    .name	= "emul_ck",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &func_54m_clk,
+};
+
+static struct clk sdma_fclk = {
+    .name	= "sdma_fclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &l3_clk,
+};
+
+static struct clk sdma_iclk = {
+    .name	= "sdma_iclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &core_l3_iclk, /* core_l4_iclk for the configuration port */
+};
+
+static struct clk i2c1_fclk = {
+    .name	= "i2c1.fclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &func_12m_clk,
+    .divisor	= 1,
+};
+
+static struct clk i2c1_iclk = {
+    .name	= "i2c1.iclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &core_l4_iclk,
+};
+
+static struct clk i2c2_fclk = {
+    .name	= "i2c2.fclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &func_12m_clk,
+    .divisor	= 1,
+};
+
+static struct clk i2c2_iclk = {
+    .name	= "i2c2.iclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &core_l4_iclk,
+};
+
+static struct clk gpio_dbclk[4] = {
+    {
+        .name	= "gpio1_dbclk",
+        .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+        .parent	= &wu_32k_clk,
+    }, {
+        .name	= "gpio2_dbclk",
+        .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+        .parent	= &wu_32k_clk,
+    }, {
+        .name	= "gpio3_dbclk",
+        .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+        .parent	= &wu_32k_clk,
+    }, {
+        .name	= "gpio4_dbclk",
+        .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+        .parent	= &wu_32k_clk,
+    },
+};
+
+static struct clk gpio_iclk = {
+    .name	= "gpio_iclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &wu_l4_iclk,
+};
+
+static struct clk mmc_fck = {
+    .name	= "mmc_fclk",
+    .flags	= CLOCK_IN_OMAP242X,
+    .parent	= &func_96m_clk,
+};
+
+static struct clk mmc_ick = {
+    .name	= "mmc_iclk",
+    .flags	= CLOCK_IN_OMAP242X,
+    .parent	= &core_l4_iclk,
+};
+
+static struct clk spi_fclk[3] = {
+    {
+        .name	= "spi1_fclk",
+        .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+        .parent	= &func_48m_clk,
+    }, {
+        .name	= "spi2_fclk",
+        .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+        .parent	= &func_48m_clk,
+    }, {
+        .name	= "spi3_fclk",
+        .flags	= CLOCK_IN_OMAP243X,
+        .parent	= &func_48m_clk,
+    },
+};
+
+static struct clk dss_clk[2] = {
+    {
+        .name	= "dss_clk1",
+        .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+        .parent	= &core_clk,
+    }, {
+        .name	= "dss_clk2",
+        .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+        .parent	= &sys_clk,
+    },
+};
+
+static struct clk dss_54m_clk = {
+    .name	= "dss_54m_clk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &func_54m_clk,
+};
+
+static struct clk dss_l3_iclk = {
+    .name	= "dss_l3_iclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &core_l3_iclk,
+};
+
+static struct clk dss_l4_iclk = {
+    .name	= "dss_l4_iclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    .parent	= &core_l4_iclk,
+};
+
+static struct clk spi_iclk[3] = {
+    {
+        .name	= "spi1_iclk",
+        .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+        .parent	= &core_l4_iclk,
+    }, {
+        .name	= "spi2_iclk",
+        .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+        .parent	= &core_l4_iclk,
+    }, {
+        .name	= "spi3_iclk",
+        .flags	= CLOCK_IN_OMAP243X,
+        .parent	= &core_l4_iclk,
+    },
+};
+
+static struct clk omapctrl_clk = {
+    .name	= "omapctrl_iclk",
+    .flags	= CLOCK_IN_OMAP242X | CLOCK_IN_OMAP243X,
+    /* XXX Should be in WKUP domain */
+    .parent	= &core_l4_iclk,
 };
 
 static struct clk *onchip_clks[] = {
+    /* OMAP 1 */
+
     /* non-ULPD clocks */
     &xtal_osc12m,
     &xtal_osc32k,
@@ -572,6 +1006,80 @@ static struct clk *onchip_clks[] = {
     /* Virtual clocks */
     &i2c_fck,
     &i2c_ick,
+
+    /* OMAP 2 */
+
+    &apll_96m,
+    &apll_54m,
+    &sys_clk,
+    &sleep_clk,
+    &dpll_ck,
+    &dpll_x2_ck,
+    &wdt1_sys_clk,
+    &func_96m_clk,
+    &func_48m_clk,
+    &func_12m_clk,
+    &func_54m_clk,
+    &sys_clkout,
+    &sys_clkout2,
+    &core_clk,
+    &l3_clk,
+    &core_l4_iclk,
+    &wu_l4_iclk,
+    &core_l3_iclk,
+    &core_l4_usb_clk,
+    &wu_gpt1_clk,
+    &wu_32k_clk,
+    &uart1_fclk,
+    &uart1_iclk,
+    &uart2_fclk,
+    &uart2_iclk,
+    &uart3_fclk,
+    &uart3_iclk,
+    &mpu_fclk,
+    &mpu_iclk,
+    &int_m_fclk,
+    &int_m_iclk,
+    &core_gpt2_clk,
+    &core_gpt3_clk,
+    &core_gpt4_clk,
+    &core_gpt5_clk,
+    &core_gpt6_clk,
+    &core_gpt7_clk,
+    &core_gpt8_clk,
+    &core_gpt9_clk,
+    &core_gpt10_clk,
+    &core_gpt11_clk,
+    &core_gpt12_clk,
+    &mcbsp1_clk,
+    &mcbsp2_clk,
+    &emul_clk,
+    &sdma_fclk,
+    &sdma_iclk,
+    &i2c1_fclk,
+    &i2c1_iclk,
+    &i2c2_fclk,
+    &i2c2_iclk,
+    &gpio_dbclk[0],
+    &gpio_dbclk[1],
+    &gpio_dbclk[2],
+    &gpio_dbclk[3],
+    &gpio_iclk,
+    &mmc_fck,
+    &mmc_ick,
+    &spi_fclk[0],
+    &spi_iclk[0],
+    &spi_fclk[1],
+    &spi_iclk[1],
+    &spi_fclk[2],
+    &spi_iclk[2],
+    &dss_clk[0],
+    &dss_clk[1],
+    &dss_54m_clk,
+    &dss_l3_iclk,
+    &dss_l4_iclk,
+    &omapctrl_clk,
+
     0
 };
 
@@ -727,6 +1235,12 @@ void omap_clk_init(struct omap_mpu_state_s *mpu)
         flag = CLOCK_IN_OMAP310;
     else if (cpu_is_omap1510(mpu))
         flag = CLOCK_IN_OMAP1510;
+    else if (cpu_is_omap2410(mpu) || cpu_is_omap2420(mpu))
+        flag = CLOCK_IN_OMAP242X;
+    else if (cpu_is_omap2430(mpu))
+        flag = CLOCK_IN_OMAP243X;
+    else if (cpu_is_omap3430(mpu))
+        flag = CLOCK_IN_OMAP243X;
     else
         return;
 
diff --git a/hw/omap_dma.c b/hw/omap_dma.c
index 1835826..6c0bd82 100644
--- a/hw/omap_dma.c
+++ b/hw/omap_dma.c
@@ -28,12 +28,15 @@ struct omap_dma_channel_s {
     /* transfer data */
     int burst[2];
     int pack[2];
+    int endian[2];
+    int endian_lock[2];
+    int translate[2];
     enum omap_dma_port port[2];
     target_phys_addr_t addr[2];
     omap_dma_addressing_t mode[2];
-    uint16_t elements;
+    uint32_t elements;
     uint16_t frames;
-    int16_t frame_index[2];
+    int32_t frame_index[2];
     int16_t element_index[2];
     int data_type;
 
@@ -41,6 +44,7 @@ struct omap_dma_channel_s {
     int transparent_copy;
     int constant_fill;
     uint32_t color;
+    int prefetch;
 
     /* auto init and linked channel data */
     int end_prog;
@@ -52,11 +56,13 @@ struct omap_dma_channel_s {
     /* interruption data */
     int interrupts;
     int status;
+    int cstatus;
 
     /* state data */
     int active;
     int enable;
     int sync;
+    int src_sync;
     int pending_request;
     int waiting_end_prog;
     uint16_t cpc;
@@ -75,16 +81,21 @@ struct omap_dma_channel_s {
         target_phys_addr_t src, dest;
         int frame;
         int element;
+        int pck_element;
         int frame_delta[2];
         int elem_delta[2];
         int frames;
         int elements;
+        int pck_elements;
     } active_set;
 
     /* unused parameters */
+    int write_mode;
     int priority;
     int interleave_disabled;
     int type;
+    int suspend;
+    int buf_disable;
 };
 
 struct omap_dma_s {
@@ -93,15 +104,21 @@ struct omap_dma_s {
     target_phys_addr_t base;
     omap_clk clk;
     int64_t delay;
-    uint32_t drq;
+    uint64_t drq;
+    qemu_irq irq[4];
+    void (*intr_update)(struct omap_dma_s *s);
     enum omap_dma_model model;
     int omap_3_1_mapping_disabled;
 
-    uint16_t gcr;
+    uint32_t gcr;
+    uint32_t ocp;
+    uint32_t caps[5];
+    uint32_t irqen[4];
+    uint32_t irqstat[4];
     int run_count;
 
     int chans;
-    struct omap_dma_channel_s ch[16];
+    struct omap_dma_channel_s ch[32];
     struct omap_dma_lcd_channel_s lcd_ch;
 };
 
@@ -113,23 +130,13 @@ struct omap_dma_s {
 #define LAST_FRAME_INTR (1 << 4)
 #define END_BLOCK_INTR  (1 << 5)
 #define SYNC            (1 << 6)
+#define END_PKT_INTR	(1 << 7)
+#define TRANS_ERR_INTR	(1 << 8)
+#define MISALIGN_INTR	(1 << 11)
 
-static void omap_dma_interrupts_update(struct omap_dma_s *s)
+static inline void omap_dma_interrupts_update(struct omap_dma_s *s)
 {
-    struct omap_dma_channel_s *ch = s->ch;
-    int i;
-
-    if (s->omap_3_1_mapping_disabled) {
-        for (i = 0; i < s->chans; i ++, ch ++)
-            if (ch->status)
-                qemu_irq_raise(ch->irq);
-    } else {
-        /* First three interrupts are shared between two channels each. */
-        for (i = 0; i < 6; i ++, ch ++) {
-            if (ch->status || (ch->sibling && ch->sibling->status))
-                qemu_irq_raise(ch->irq);
-        }
-    }
+    return s->intr_update(s);
 }
 
 static void omap_dma_channel_load(struct omap_dma_s *s,
@@ -148,8 +155,10 @@ static void omap_dma_channel_load(struct omap_dma_s *s,
     a->dest = ch->addr[1];
     a->frames = ch->frames;
     a->elements = ch->elements;
+    a->pck_elements = ch->frame_index[!ch->src_sync];
     a->frame = 0;
     a->element = 0;
+    a->pck_element = 0;
 
     if (unlikely(!ch->elements || !ch->frames)) {
         printf("%s: bad DMA request\n", __FUNCTION__);
@@ -202,16 +211,15 @@ static void omap_dma_deactivate_channel(struct omap_dma_s *s,
     /* Update cpc */
     ch->cpc = ch->active_set.dest & 0xffff;
 
-    if (ch->pending_request && !ch->waiting_end_prog) {
+    if (ch->pending_request && !ch->waiting_end_prog && ch->enable) {
         /* Don't deactivate the channel */
         ch->pending_request = 0;
-        if (ch->enable)
-            return;
+        return;
     }
 
     /* Don't deactive the channel if it is synchronized and the DMA request is
        active */
-    if (ch->sync && (s->drq & (1 << ch->sync)) && ch->enable)
+    if (ch->sync && ch->enable && (s->drq & (1 << ch->sync)))
         return;
 
     if (ch->active) {
@@ -231,6 +239,9 @@ static void omap_dma_enable_channel(struct omap_dma_s *s,
         ch->enable = 1;
         ch->waiting_end_prog = 0;
         omap_dma_channel_load(s, ch);
+        /* TODO: theoretically if ch->sync && ch->prefetch &&
+         * !s->drq[ch->sync], we should also activate and fetch from source
+         * and then stall until signalled.  */
         if ((!ch->sync) || (s->drq & (1 << ch->sync)))
             omap_dma_activate_channel(s, ch);
     }
@@ -259,16 +270,47 @@ static void omap_dma_channel_end_prog(struct omap_dma_s *s,
     }
 }
 
+static void omap_dma_interrupts_3_1_update(struct omap_dma_s *s)
+{
+    struct omap_dma_channel_s *ch = s->ch;
+
+    /* First three interrupts are shared between two channels each. */
+    if (ch[0].status | ch[6].status)
+        qemu_irq_raise(ch[0].irq);
+    if (ch[1].status | ch[7].status)
+        qemu_irq_raise(ch[1].irq);
+    if (ch[2].status | ch[8].status)
+        qemu_irq_raise(ch[2].irq);
+    if (ch[3].status)
+        qemu_irq_raise(ch[3].irq);
+    if (ch[4].status)
+        qemu_irq_raise(ch[4].irq);
+    if (ch[5].status)
+        qemu_irq_raise(ch[5].irq);
+}
+
+static void omap_dma_interrupts_3_2_update(struct omap_dma_s *s)
+{
+    struct omap_dma_channel_s *ch = s->ch;
+    int i;
+
+    for (i = s->chans; i; ch ++, i --)
+        if (ch->status)
+            qemu_irq_raise(ch->irq);
+}
+
 static void omap_dma_enable_3_1_mapping(struct omap_dma_s *s)
 {
     s->omap_3_1_mapping_disabled = 0;
     s->chans = 9;
+    s->intr_update = omap_dma_interrupts_3_1_update;
 }
 
 static void omap_dma_disable_3_1_mapping(struct omap_dma_s *s)
 {
     s->omap_3_1_mapping_disabled = 1;
     s->chans = 16;
+    s->intr_update = omap_dma_interrupts_3_2_update;
 }
 
 static void omap_dma_process_request(struct omap_dma_s *s, int request)
@@ -358,6 +400,22 @@ static void omap_dma_channel_run(struct omap_dma_s *s)
                 if (ch->interrupts & HALF_FRAME_INTR)
                     ch->status |= HALF_FRAME_INTR;
 
+            if (ch->fs && ch->bs) {
+                a->pck_element ++;
+                /* Check if a full packet has beed transferred.  */
+                if (a->pck_element == a->pck_elements) {
+                    a->pck_element = 0;
+
+                    /* Set the END_PKT interrupt */
+                    if ((ch->interrupts & END_PKT_INTR) && !ch->src_sync)
+                        ch->status |= END_PKT_INTR;
+
+                    /* If the channel is packet-synchronized, deactivate it */
+                    if (ch->sync)
+                        omap_dma_deactivate_channel(s, ch);
+                }
+            }
+
             if (a->element == a->elements) {
                 /* End of Frame */
                 a->element = 0;
@@ -366,7 +424,7 @@ static void omap_dma_channel_run(struct omap_dma_s *s)
                 a->frame ++;
 
                 /* If the channel is frame synchronized, deactivate it */
-                if (ch->sync && ch->fs)
+                if (ch->sync && ch->fs && !ch->bs)
                     omap_dma_deactivate_channel(s, ch);
 
                 /* If the channel is async, update cpc */
@@ -414,50 +472,62 @@ void omap_dma_reset(struct omap_dma_s *s)
     int i;
 
     qemu_del_timer(s->tm);
-    s->gcr = 0x0004;
+    if (s->model < omap_dma_4)
+        s->gcr = 0x0004;
+    else
+        s->gcr = 0x00010010;
+    s->ocp = 0x00000000;
+    memset(&s->irqstat, 0, sizeof(s->irqstat));
+    memset(&s->irqen, 0, sizeof(s->irqen));
     s->drq = 0x00000000;
     s->run_count = 0;
     s->lcd_ch.src = emiff;
     s->lcd_ch.condition = 0;
     s->lcd_ch.interrupts = 0;
     s->lcd_ch.dual = 0;
-    omap_dma_enable_3_1_mapping(s);
+    if (s->model < omap_dma_4)
+        omap_dma_enable_3_1_mapping(s);
     for (i = 0; i < s->chans; i ++) {
+        s->ch[i].suspend = 0;
+        s->ch[i].prefetch = 0;
+        s->ch[i].buf_disable = 0;
+        s->ch[i].src_sync = 0;
         memset(&s->ch[i].burst, 0, sizeof(s->ch[i].burst));
         memset(&s->ch[i].port, 0, sizeof(s->ch[i].port));
         memset(&s->ch[i].mode, 0, sizeof(s->ch[i].mode));
-        memset(&s->ch[i].elements, 0, sizeof(s->ch[i].elements));
-        memset(&s->ch[i].frames, 0, sizeof(s->ch[i].frames));
         memset(&s->ch[i].frame_index, 0, sizeof(s->ch[i].frame_index));
         memset(&s->ch[i].element_index, 0, sizeof(s->ch[i].element_index));
-        memset(&s->ch[i].data_type, 0, sizeof(s->ch[i].data_type));
-        memset(&s->ch[i].transparent_copy, 0,
-                        sizeof(s->ch[i].transparent_copy));
-        memset(&s->ch[i].constant_fill, 0, sizeof(s->ch[i].constant_fill));
-        memset(&s->ch[i].color, 0, sizeof(s->ch[i].color));
-        memset(&s->ch[i].end_prog, 0, sizeof(s->ch[i].end_prog));
-        memset(&s->ch[i].repeat, 0, sizeof(s->ch[i].repeat));
-        memset(&s->ch[i].auto_init, 0, sizeof(s->ch[i].auto_init));
-        memset(&s->ch[i].link_enabled, 0, sizeof(s->ch[i].link_enabled));
-        memset(&s->ch[i].link_next_ch, 0, sizeof(s->ch[i].link_next_ch));
-        s->ch[i].interrupts = 0x0003;
-        memset(&s->ch[i].status, 0, sizeof(s->ch[i].status));
-        memset(&s->ch[i].active, 0, sizeof(s->ch[i].active));
-        memset(&s->ch[i].enable, 0, sizeof(s->ch[i].enable));
-        memset(&s->ch[i].sync, 0, sizeof(s->ch[i].sync));
-        memset(&s->ch[i].pending_request, 0, sizeof(s->ch[i].pending_request));
-        memset(&s->ch[i].waiting_end_prog, 0,
-                        sizeof(s->ch[i].waiting_end_prog));
-        memset(&s->ch[i].cpc, 0, sizeof(s->ch[i].cpc));
-        memset(&s->ch[i].fs, 0, sizeof(s->ch[i].fs));
-        memset(&s->ch[i].bs, 0, sizeof(s->ch[i].bs));
-        memset(&s->ch[i].omap_3_1_compatible_disable, 0,
-                        sizeof(s->ch[i].omap_3_1_compatible_disable));
+        memset(&s->ch[i].endian, 0, sizeof(s->ch[i].endian));
+        memset(&s->ch[i].endian_lock, 0, sizeof(s->ch[i].endian_lock));
+        memset(&s->ch[i].translate, 0, sizeof(s->ch[i].translate));
+        s->ch[i].write_mode = 0;
+        s->ch[i].data_type = 0;
+        s->ch[i].transparent_copy = 0;
+        s->ch[i].constant_fill = 0;
+        s->ch[i].color = 0x00000000;
+        s->ch[i].end_prog = 0;
+        s->ch[i].repeat = 0;
+        s->ch[i].auto_init = 0;
+        s->ch[i].link_enabled = 0;
+        if (s->model < omap_dma_4)
+            s->ch[i].interrupts = 0x0003;
+        else
+            s->ch[i].interrupts = 0x0000;
+        s->ch[i].status = 0;
+        s->ch[i].cstatus = 0;
+        s->ch[i].active = 0;
+        s->ch[i].enable = 0;
+        s->ch[i].sync = 0;
+        s->ch[i].pending_request = 0;
+        s->ch[i].waiting_end_prog = 0;
+        s->ch[i].cpc = 0x0000;
+        s->ch[i].fs = 0;
+        s->ch[i].bs = 0;
+        s->ch[i].omap_3_1_compatible_disable = 0;
         memset(&s->ch[i].active_set, 0, sizeof(s->ch[i].active_set));
-        memset(&s->ch[i].priority, 0, sizeof(s->ch[i].priority));
-        memset(&s->ch[i].interleave_disabled, 0,
-                        sizeof(s->ch[i].interleave_disabled));
-        memset(&s->ch[i].type, 0, sizeof(s->ch[i].type));
+        s->ch[i].priority = 0;
+        s->ch[i].interleave_disabled = 0;
+        s->ch[i].type = 0;
     }
 }
 
@@ -476,7 +546,7 @@ static int omap_dma_ch_reg_read(struct omap_dma_s *s,
         break;
 
     case 0x02:	/* SYS_DMA_CCR_CH0 */
-        if (s->model == omap_dma_3_1)
+        if (s->model <= omap_dma_3_1)
             *value = 0 << 10;			/* FIFO_FLUSH reads as 0 */
         else
             *value = ch->omap_3_1_compatible_disable << 10;
@@ -596,11 +666,11 @@ static int omap_dma_ch_reg_write(struct omap_dma_s *s,
         ch->burst[0] = (value & 0x0180) >> 7;
         ch->pack[0] = (value & 0x0040) >> 6;
         ch->port[0] = (enum omap_dma_port) ((value & 0x003c) >> 2);
-        ch->data_type = (1 << (value & 3));
-        if (ch->port[0] >= omap_dma_port_last)
+        ch->data_type = 1 << (value & 3);
+        if (ch->port[0] >= __omap_dma_port_last)
             printf("%s: invalid DMA port %i\n", __FUNCTION__,
                             ch->port[0]);
-        if (ch->port[1] >= omap_dma_port_last)
+        if (ch->port[1] >= __omap_dma_port_last)
             printf("%s: invalid DMA port %i\n", __FUNCTION__,
                             ch->port[1]);
         if ((value & 3) == 3)
@@ -611,7 +681,7 @@ static int omap_dma_ch_reg_write(struct omap_dma_s *s,
         ch->mode[1] = (omap_dma_addressing_t) ((value & 0xc000) >> 14);
         ch->mode[0] = (omap_dma_addressing_t) ((value & 0x3000) >> 12);
         ch->end_prog = (value & 0x0800) >> 11;
-        if (s->model > omap_dma_3_1)
+        if (s->model >= omap_dma_3_2)
             ch->omap_3_1_compatible_disable  = (value >> 10) & 0x1;
         ch->repeat = (value & 0x0200) >> 9;
         ch->auto_init = (value & 0x0100) >> 8;
@@ -630,7 +700,7 @@ static int omap_dma_ch_reg_write(struct omap_dma_s *s,
         break;
 
     case 0x04:	/* SYS_DMA_CICR_CH0 */
-        ch->interrupts = value;
+        ch->interrupts = value & 0x3f;
         break;
 
     case 0x06:	/* SYS_DMA_CSR_CH0 */
@@ -696,7 +766,7 @@ static int omap_dma_ch_reg_write(struct omap_dma_s *s,
         break;
 
     case 0x24:	/* DMA_CCR2 */
-        ch->bs  = (value >> 2) & 0x1;
+        ch->bs = (value >> 2) & 0x1;
         ch->transparent_copy = (value >> 1) & 0x1;
         ch->constant_fill = value & 0x1;
         break;
@@ -1126,48 +1196,29 @@ static int omap_dma_sys_read(struct omap_dma_s *s, int offset,
         break;
 
     case 0x44e:	/* DMA_CAPS_0_U */
-        *ret = (1 << 3) | /* Constant Fill Capacity */
-            (1 << 2);     /* Transparent BLT Capacity */
+        *ret = (s->caps[0] >> 16) & 0xffff;
         break;
-
     case 0x450:	/* DMA_CAPS_0_L */
-    case 0x452:	/* DMA_CAPS_1_U */
-        *ret = 0;
+        *ret = (s->caps[0] >>  0) & 0xffff;
         break;
 
+    case 0x452:	/* DMA_CAPS_1_U */
+        *ret = (s->caps[1] >> 16) & 0xffff;
+        break;
     case 0x454:	/* DMA_CAPS_1_L */
-        *ret = (1 << 1); /* 1-bit palletized capability */
+        *ret = (s->caps[1] >>  0) & 0xffff;
         break;
 
     case 0x456:	/* DMA_CAPS_2 */
-        *ret = (1 << 8) | /* SSDIC */
-            (1 << 7) |    /* DDIAC */
-            (1 << 6) |    /* DSIAC */
-            (1 << 5) |    /* DPIAC */
-            (1 << 4) |    /* DCAC  */
-            (1 << 3) |    /* SDIAC */
-            (1 << 2) |    /* SSIAC */
-            (1 << 1) |    /* SPIAC */
-            1;            /* SCAC  */
+        *ret = s->caps[2];
         break;
 
     case 0x458:	/* DMA_CAPS_3 */
-        *ret = (1 << 5) | /* CCC */
-            (1 << 4) |    /* IC  */
-            (1 << 3) |    /* ARC */
-            (1 << 2) |    /* AEC */
-            (1 << 1) |    /* FSC */
-            1;            /* ESC */
+        *ret = s->caps[3];
         break;
 
     case 0x45a:	/* DMA_CAPS_4 */
-        *ret = (1 << 6) | /* SSC  */
-            (1 << 5) |    /* BIC  */
-            (1 << 4) |    /* LFIC */
-            (1 << 3) |    /* FIC  */
-            (1 << 2) |    /* HFIC */
-            (1 << 1) |    /* EDIC */
-            1;            /* TOIC */
+        *ret = s->caps[4];
         break;
 
     case 0x460:	/* DMA_PCh2_SR */
@@ -1193,7 +1244,7 @@ static uint32_t omap_dma_read(void *opaque, target_phys_addr_t addr)
 
     switch (offset) {
     case 0x300 ... 0x3fe:
-        if (s->model == omap_dma_3_1 || !s->omap_3_1_mapping_disabled) {
+        if (s->model <= omap_dma_3_1 || !s->omap_3_1_mapping_disabled) {
             if (omap_dma_3_1_lcd_read(&s->lcd_ch, offset, &ret))
                 break;
             return ret;
@@ -1207,7 +1258,7 @@ static uint32_t omap_dma_read(void *opaque, target_phys_addr_t addr)
         return ret;
 
     case 0x404 ... 0x4fe:
-        if (s->model == omap_dma_3_1)
+        if (s->model <= omap_dma_3_1)
             break;
         /* Fall through. */
     case 0x400:
@@ -1236,7 +1287,7 @@ static void omap_dma_write(void *opaque, target_phys_addr_t addr,
 
     switch (offset) {
     case 0x300 ... 0x3fe:
-        if (s->model == omap_dma_3_1 || !s->omap_3_1_mapping_disabled) {
+        if (s->model <= omap_dma_3_1 || !s->omap_3_1_mapping_disabled) {
             if (omap_dma_3_1_lcd_write(&s->lcd_ch, offset, value))
                 break;
             return;
@@ -1250,7 +1301,7 @@ static void omap_dma_write(void *opaque, target_phys_addr_t addr,
         return;
 
     case 0x404 ... 0x4fe:
-        if (s->model == omap_dma_3_1)
+        if (s->model <= omap_dma_3_1)
             break;
     case 0x400:
         /* Fall through. */
@@ -1285,7 +1336,7 @@ static CPUWriteMemoryFunc *omap_dma_writefn[] = {
 static void omap_dma_request(void *opaque, int drq, int req)
 {
     struct omap_dma_s *s = (struct omap_dma_s *) opaque;
-    /* The request pins are level triggered.  */
+    /* The request pins are level triggered in QEMU.  */
     if (req) {
         if (~s->drq & (1 << drq)) {
             s->drq |= 1 << drq;
@@ -1310,6 +1361,52 @@ static void omap_dma_clk_update(void *opaque, int line, int on)
     }
 }
 
+static void omap_dma_setcaps(struct omap_dma_s *s)
+{
+    switch (s->model) {
+    default:
+    case omap_dma_3_1:
+        break;
+    case omap_dma_3_2:
+    case omap_dma_4:
+        /* XXX Only available for sDMA */
+        s->caps[0] =
+                (1 << 19) |	/* Constant Fill Capability */
+                (1 << 18);	/* Transparent BLT Capability */
+        s->caps[1] =
+                (1 << 1);	/* 1-bit palettized capability (DMA 3.2 only) */
+        s->caps[2] =
+                (1 << 8) |	/* SEPARATE_SRC_AND_DST_INDEX_CPBLTY */
+                (1 << 7) |	/* DST_DOUBLE_INDEX_ADRS_CPBLTY */
+                (1 << 6) |	/* DST_SINGLE_INDEX_ADRS_CPBLTY */
+                (1 << 5) |	/* DST_POST_INCRMNT_ADRS_CPBLTY */
+                (1 << 4) |	/* DST_CONST_ADRS_CPBLTY */
+                (1 << 3) |	/* SRC_DOUBLE_INDEX_ADRS_CPBLTY */
+                (1 << 2) |	/* SRC_SINGLE_INDEX_ADRS_CPBLTY */
+                (1 << 1) |	/* SRC_POST_INCRMNT_ADRS_CPBLTY */
+                (1 << 0);	/* SRC_CONST_ADRS_CPBLTY */
+        s->caps[3] =
+                (1 << 6) |	/* BLOCK_SYNCHR_CPBLTY (DMA 4 only) */
+                (1 << 7) |	/* PKT_SYNCHR_CPBLTY (DMA 4 only) */
+                (1 << 5) |	/* CHANNEL_CHAINING_CPBLTY */
+                (1 << 4) |	/* LCh_INTERLEAVE_CPBLTY */
+                (1 << 3) |	/* AUTOINIT_REPEAT_CPBLTY (DMA 3.2 only) */
+                (1 << 2) |	/* AUTOINIT_ENDPROG_CPBLTY (DMA 3.2 only) */
+                (1 << 1) |	/* FRAME_SYNCHR_CPBLTY */
+                (1 << 0);	/* ELMNT_SYNCHR_CPBLTY */
+        s->caps[4] =
+                (1 << 7) |	/* PKT_INTERRUPT_CPBLTY (DMA 4 only) */
+                (1 << 6) |	/* SYNC_STATUS_CPBLTY */
+                (1 << 5) |	/* BLOCK_INTERRUPT_CPBLTY */
+                (1 << 4) |	/* LAST_FRAME_INTERRUPT_CPBLTY */
+                (1 << 3) |	/* FRAME_INTERRUPT_CPBLTY */
+                (1 << 2) |	/* HALF_FRAME_INTERRUPT_CPBLTY */
+                (1 << 1) |	/* EVENT_DROP_INTERRUPT_CPBLTY */
+                (1 << 0);	/* TIMEOUT_INTERRUPT_CPBLTY (DMA 3.2 only) */
+        break;
+    }
+}
+
 struct omap_dma_s *omap_dma_init(target_phys_addr_t base, qemu_irq *irqs,
                 qemu_irq lcd_irq, struct omap_mpu_state_s *mpu, omap_clk clk,
                 enum omap_dma_model model)
@@ -1318,7 +1415,7 @@ struct omap_dma_s *omap_dma_init(target_phys_addr_t base, qemu_irq *irqs,
     struct omap_dma_s *s = (struct omap_dma_s *)
             qemu_mallocz(sizeof(struct omap_dma_s));
 
-    if (model == omap_dma_3_1) {
+    if (model <= omap_dma_3_1) {
         num_irqs = 6;
         memsize = 0x800;
     } else {
@@ -1331,6 +1428,7 @@ struct omap_dma_s *omap_dma_init(target_phys_addr_t base, qemu_irq *irqs,
     s->clk = clk;
     s->lcd_ch.irq = lcd_irq;
     s->lcd_ch.mpu = mpu;
+    omap_dma_setcaps(s);
     while (num_irqs --)
         s->ch[num_irqs].irq = irqs[num_irqs];
     for (i = 0; i < 3; i ++) {
@@ -1350,6 +1448,393 @@ struct omap_dma_s *omap_dma_init(target_phys_addr_t base, qemu_irq *irqs,
     return s;
 }
 
+static void omap_dma_interrupts_4_update(struct omap_dma_s *s)
+{
+    struct omap_dma_channel_s *ch = s->ch;
+    uint32_t bmp, bit;
+
+    for (bmp = 0, bit = 1; bit; ch ++, bit <<= 1)
+        if (ch->status) {
+            bmp |= bit;
+            ch->cstatus |= ch->status;
+            ch->status = 0;
+        }
+    if ((s->irqstat[0] |= s->irqen[0] & bmp))
+        qemu_irq_raise(s->irq[0]);
+    if ((s->irqstat[1] |= s->irqen[1] & bmp))
+        qemu_irq_raise(s->irq[1]);
+    if ((s->irqstat[2] |= s->irqen[2] & bmp))
+        qemu_irq_raise(s->irq[2]);
+    if ((s->irqstat[3] |= s->irqen[3] & bmp))
+        qemu_irq_raise(s->irq[3]);
+}
+
+static uint32_t omap_dma4_read(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_dma_s *s = (struct omap_dma_s *) opaque;
+    int irqn = 0, chnum, offset = addr - s->base;
+    struct omap_dma_channel_s *ch;
+
+    switch (offset) {
+    case 0x00:	/* DMA4_REVISION */
+        return 0x40;
+
+    case 0x14:	/* DMA4_IRQSTATUS_L3 */
+        irqn ++;
+    case 0x10:	/* DMA4_IRQSTATUS_L2 */
+        irqn ++;
+    case 0x0c:	/* DMA4_IRQSTATUS_L1 */
+        irqn ++;
+    case 0x08:	/* DMA4_IRQSTATUS_L0 */
+        return s->irqstat[irqn];
+
+    case 0x24:	/* DMA4_IRQENABLE_L3 */
+        irqn ++;
+    case 0x20:	/* DMA4_IRQENABLE_L2 */
+        irqn ++;
+    case 0x1c:	/* DMA4_IRQENABLE_L1 */
+        irqn ++;
+    case 0x18:	/* DMA4_IRQENABLE_L0 */
+        return s->irqen[irqn];
+
+    case 0x28:	/* DMA4_SYSSTATUS */
+        return 1;						/* RESETDONE */
+
+    case 0x2c:	/* DMA4_OCP_SYSCONFIG */
+        return s->ocp;
+
+    case 0x64:	/* DMA4_CAPS_0 */
+        return s->caps[0];
+    case 0x6c:	/* DMA4_CAPS_2 */
+        return s->caps[2];
+    case 0x70:	/* DMA4_CAPS_3 */
+        return s->caps[3];
+    case 0x74:	/* DMA4_CAPS_4 */
+        return s->caps[4];
+
+    case 0x78:	/* DMA4_GCR */
+        return s->gcr;
+
+    case 0x80 ... 0xfff:
+        offset -= 0x80;
+        chnum = offset / 0x60;
+        ch = s->ch + chnum;
+        offset -= chnum * 0x60;
+        break;
+
+    default:
+        OMAP_BAD_REG(addr);
+        return 0;
+    }
+
+    /* Per-channel registers */
+    switch (offset) {
+    case 0x00:	/* DMA4_CCR */
+        return (ch->buf_disable << 25) |
+                (ch->src_sync << 24) |
+                (ch->prefetch << 23) |
+                ((ch->sync & 0x60) << 14) |
+                (ch->bs << 18) |
+                (ch->transparent_copy << 17) |
+                (ch->constant_fill << 16) |
+                (ch->mode[1] << 14) |
+                (ch->mode[0] << 12) |
+                (0 << 10) | (0 << 9) |
+                (ch->suspend << 8) |
+                (ch->enable << 7) |
+                (ch->priority << 6) |
+                (ch->fs << 5) | (ch->sync & 0x1f);
+
+    case 0x04:	/* DMA4_CLNK_CTRL */
+        return (ch->link_enabled << 15) | ch->link_next_ch;
+
+    case 0x08:	/* DMA4_CICR */
+        return ch->interrupts;
+
+    case 0x0c:	/* DMA4_CSR */
+        return ch->cstatus;
+
+    case 0x10:	/* DMA4_CSDP */
+        return (ch->endian[0] << 21) |
+                (ch->endian_lock[0] << 20) |
+                (ch->endian[1] << 19) |
+                (ch->endian_lock[1] << 18) |
+                (ch->write_mode << 16) |
+                (ch->burst[1] << 14) |
+                (ch->pack[1] << 13) |
+                (ch->translate[1] << 9) |
+                (ch->burst[0] << 7) |
+                (ch->pack[0] << 6) |
+                (ch->translate[0] << 2) |
+                (ch->data_type >> 1);
+
+    case 0x14:	/* DMA4_CEN */
+        return ch->elements;
+
+    case 0x18:	/* DMA4_CFN */
+        return ch->frames;
+
+    case 0x1c:	/* DMA4_CSSA */
+        return ch->addr[0];
+
+    case 0x20:	/* DMA4_CDSA */
+        return ch->addr[1];
+
+    case 0x24:	/* DMA4_CSEI */
+        return ch->element_index[0];
+
+    case 0x28:	/* DMA4_CSFI */
+        return ch->frame_index[0];
+
+    case 0x2c:	/* DMA4_CDEI */
+        return ch->element_index[1];
+
+    case 0x30:	/* DMA4_CDFI */
+        return ch->frame_index[1];
+
+    case 0x34:	/* DMA4_CSAC */
+        return ch->active_set.src & 0xffff;
+
+    case 0x38:	/* DMA4_CDAC */
+        return ch->active_set.dest & 0xffff;
+
+    case 0x3c:	/* DMA4_CCEN */
+        return ch->active_set.element;
+
+    case 0x40:	/* DMA4_CCFN */
+        return ch->active_set.frame;
+
+    case 0x44:	/* DMA4_COLOR */
+        /* XXX only in sDMA */
+        return ch->color;
+
+    default:
+        OMAP_BAD_REG(addr);
+        return 0;
+    }
+}
+
+static void omap_dma4_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct omap_dma_s *s = (struct omap_dma_s *) opaque;
+    int chnum, irqn = 0, offset = addr - s->base;
+    struct omap_dma_channel_s *ch;
+
+    switch (offset) {
+    case 0x14:	/* DMA4_IRQSTATUS_L3 */
+        irqn ++;
+    case 0x10:	/* DMA4_IRQSTATUS_L2 */
+        irqn ++;
+    case 0x0c:	/* DMA4_IRQSTATUS_L1 */
+        irqn ++;
+    case 0x08:	/* DMA4_IRQSTATUS_L0 */
+        s->irqstat[irqn] &= ~value;
+        if (!s->irqstat[irqn])
+            qemu_irq_lower(s->irq[irqn]);
+        return;
+
+    case 0x24:	/* DMA4_IRQENABLE_L3 */
+        irqn ++;
+    case 0x20:	/* DMA4_IRQENABLE_L2 */
+        irqn ++;
+    case 0x1c:	/* DMA4_IRQENABLE_L1 */
+        irqn ++;
+    case 0x18:	/* DMA4_IRQENABLE_L0 */
+        s->irqen[irqn] = value;
+        return;
+
+    case 0x2c:	/* DMA4_OCP_SYSCONFIG */
+        if (value & 2)						/* SOFTRESET */
+            omap_dma_reset(s);
+        s->ocp = value & 0x3321;
+        if (((s->ocp >> 12) & 3) == 3)				/* MIDLEMODE */
+            fprintf(stderr, "%s: invalid DMA power mode\n", __FUNCTION__);
+        return;
+
+    case 0x78:	/* DMA4_GCR */
+        s->gcr = value & 0x00ff00ff;
+	if ((value & 0xff) == 0x00)		/* MAX_CHANNEL_FIFO_DEPTH */
+            fprintf(stderr, "%s: wrong FIFO depth in GCR\n", __FUNCTION__);
+        return;
+
+    case 0x80 ... 0xfff:
+        offset -= 0x80;
+        chnum = offset / 0x60;
+        ch = s->ch + chnum;
+        offset -= chnum * 0x60;
+        break;
+
+    case 0x00:	/* DMA4_REVISION */
+    case 0x28:	/* DMA4_SYSSTATUS */
+    case 0x64:	/* DMA4_CAPS_0 */
+    case 0x6c:	/* DMA4_CAPS_2 */
+    case 0x70:	/* DMA4_CAPS_3 */
+    case 0x74:	/* DMA4_CAPS_4 */
+        OMAP_RO_REG(addr);
+        return;
+
+    default:
+        OMAP_BAD_REG(addr);
+        return;
+    }
+
+    /* Per-channel registers */
+    switch (offset) {
+    case 0x00:	/* DMA4_CCR */
+        ch->buf_disable = (value >> 25) & 1;
+        ch->src_sync = (value >> 24) & 1;	/* XXX For CamDMA must be 1 */
+        if (ch->buf_disable && !ch->src_sync)
+            fprintf(stderr, "%s: Buffering disable is not allowed in "
+                            "destination synchronised mode\n", __FUNCTION__);
+        ch->prefetch = (value >> 23) & 1;
+        ch->bs = (value >> 18) & 1;
+        ch->transparent_copy = (value >> 17) & 1;
+        ch->constant_fill = (value >> 16) & 1;
+        ch->mode[1] = (omap_dma_addressing_t) ((value & 0xc000) >> 14);
+        ch->mode[0] = (omap_dma_addressing_t) ((value & 0x3000) >> 12);
+        ch->suspend = (value & 0x0100) >> 8;
+        ch->priority = (value & 0x0040) >> 6;
+        ch->fs = (value & 0x0020) >> 5;
+        if (ch->fs && ch->bs && ch->mode[0] && ch->mode[1])
+            fprintf(stderr, "%s: For a packet transfer at least one port "
+                            "must be constant-addressed\n", __FUNCTION__);
+        ch->sync = (value & 0x001f) | ((value >> 14) & 0x0060);
+        /* XXX must be 0x01 for CamDMA */
+
+        if (value & 0x0080)
+            omap_dma_enable_channel(s, ch);
+        else
+            omap_dma_disable_channel(s, ch);
+
+        break;
+
+    case 0x04:	/* DMA4_CLNK_CTRL */
+        ch->link_enabled = (value >> 15) & 0x1;
+        ch->link_next_ch = value & 0x1f;
+        break;
+
+    case 0x08:	/* DMA4_CICR */
+        ch->interrupts = value & 0x09be;
+        break;
+
+    case 0x0c:	/* DMA4_CSR */
+        ch->cstatus &= ~value;
+        break;
+
+    case 0x10:	/* DMA4_CSDP */
+        ch->endian[0] =(value >> 21) & 1;
+        ch->endian_lock[0] =(value >> 20) & 1;
+        ch->endian[1] =(value >> 19) & 1;
+        ch->endian_lock[1] =(value >> 18) & 1;
+        if (ch->endian[0] != ch->endian[1])
+            fprintf(stderr, "%s: DMA endianned conversion enable attempt\n",
+                            __FUNCTION__);
+        ch->write_mode = (value >> 16) & 3;
+        ch->burst[1] = (value & 0xc000) >> 14;
+        ch->pack[1] = (value & 0x2000) >> 13;
+        ch->translate[1] = (value & 0x1e00) >> 9;
+        ch->burst[0] = (value & 0x0180) >> 7;
+        ch->pack[0] = (value & 0x0040) >> 6;
+        ch->translate[0] = (value & 0x003c) >> 2;
+        if (ch->translate[0] | ch->translate[1])
+            fprintf(stderr, "%s: bad MReqAddressTranslate sideband signal\n",
+                            __FUNCTION__);
+        ch->data_type = 1 << (value & 3);
+        if ((value & 3) == 3)
+            printf("%s: bad data_type for DMA channel\n", __FUNCTION__);
+        break;
+
+    case 0x14:	/* DMA4_CEN */
+        ch->elements = value & 0xffffff;
+        break;
+
+    case 0x18:	/* DMA4_CFN */
+        ch->frames = value & 0xffff;
+        break;
+
+    case 0x1c:	/* DMA4_CSSA */
+        ch->addr[0] = (target_phys_addr_t) (uint32_t) value;
+        break;
+
+    case 0x20:	/* DMA4_CDSA */
+        ch->addr[1] = (target_phys_addr_t) (uint32_t) value;
+        break;
+
+    case 0x24:	/* DMA4_CSEI */
+        ch->element_index[0] = (int16_t) value;
+        break;
+
+    case 0x28:	/* DMA4_CSFI */
+        ch->frame_index[0] = (int32_t) value;
+        break;
+
+    case 0x2c:	/* DMA4_CDEI */
+        ch->element_index[1] = (int16_t) value;
+        break;
+
+    case 0x30:	/* DMA4_CDFI */
+        ch->frame_index[1] = (int32_t) value;
+        break;
+
+    case 0x44:	/* DMA4_COLOR */
+        /* XXX only in sDMA */
+        ch->color = value;
+        break;
+
+    case 0x34:	/* DMA4_CSAC */
+    case 0x38:	/* DMA4_CDAC */
+    case 0x3c:	/* DMA4_CCEN */
+    case 0x40:	/* DMA4_CCFN */
+        OMAP_RO_REG(addr);
+        break;
+
+    default:
+        OMAP_BAD_REG(addr);
+    }
+}
+
+static CPUReadMemoryFunc *omap_dma4_readfn[] = {
+    omap_badwidth_read16,
+    omap_dma4_read,
+    omap_dma4_read,
+};
+
+static CPUWriteMemoryFunc *omap_dma4_writefn[] = {
+    omap_badwidth_write16,
+    omap_dma4_write,
+    omap_dma4_write,
+};
+
+struct omap_dma_s *omap_dma4_init(target_phys_addr_t base, qemu_irq *irqs,
+                struct omap_mpu_state_s *mpu, int fifo,
+                int chans, omap_clk iclk, omap_clk fclk)
+{
+    int iomemtype;
+    struct omap_dma_s *s = (struct omap_dma_s *)
+            qemu_mallocz(sizeof(struct omap_dma_s));
+
+    s->base = base;
+    s->model = omap_dma_4;
+    s->chans = chans;
+    s->mpu = mpu;
+    s->clk = fclk;
+    memcpy(&s->irq, irqs, sizeof(s->irq));
+    s->intr_update = omap_dma_interrupts_4_update;
+    omap_dma_setcaps(s);
+    s->tm = qemu_new_timer(vm_clock, (QEMUTimerCB *) omap_dma_channel_run, s);
+    omap_clk_adduser(s->clk, qemu_allocate_irqs(omap_dma_clk_update, s, 1)[0]);
+    mpu->drq = qemu_allocate_irqs(omap_dma_request, s, 64);
+    omap_dma_reset(s);
+    omap_dma_clk_update(s, 0, 1);
+
+    iomemtype = cpu_register_io_memory(0, omap_dma4_readfn,
+                    omap_dma4_writefn, s);
+    cpu_register_physical_memory(s->base, 0x1000, iomemtype);
+
+    return s;
+}
+
 struct omap_dma_lcd_channel_s *omap_dma_get_lcdch(struct omap_dma_s *s)
 {
     return &s->lcd_ch;
diff --git a/hw/omap_dss.c b/hw/omap_dss.c
new file mode 100644
index 0000000..1c16802
--- /dev/null
+++ b/hw/omap_dss.c
@@ -0,0 +1,1088 @@
+/*
+ * OMAP2 Display Subsystem.
+ *
+ * Copyright (C) 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 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+#include "hw.h"
+#include "console.h"
+#include "omap.h"
+
+struct omap_dss_s {
+    target_phys_addr_t diss_base;
+    target_phys_addr_t disc_base;
+    target_phys_addr_t rfbi_base;
+    target_phys_addr_t venc_base;
+    target_phys_addr_t im3_base;
+    qemu_irq irq;
+    qemu_irq drq;
+    DisplayState *state;
+
+    int autoidle;
+    int control;
+    int enable;
+
+    struct omap_dss_panel_s {
+        int enable;
+        int nx;
+        int ny;
+
+        int x;
+        int y;
+    } dig, lcd;
+
+    struct {
+        uint32_t idlemode;
+        uint32_t irqst;
+        uint32_t irqen;
+        uint32_t control;
+        uint32_t config;
+        uint32_t capable;
+        uint32_t timing[3];
+        int line;
+        uint32_t bg[2];
+        uint32_t trans[2];
+
+        struct omap_dss_plane_s {
+            int enable;
+            int bpp;
+            int posx;
+            int posy;
+            int nx;
+            int ny;
+
+            target_phys_addr_t addr[3];
+
+            uint32_t attr;
+            uint32_t tresh;
+            int rowinc;
+            int colinc;
+            int wininc;
+        } l[3];
+
+        int invalidate;
+        uint16_t palette[256];
+    } dispc;
+
+    struct {
+        int idlemode;
+        uint32_t control;
+        int enable;
+        int pixels;
+        int busy;
+        int skiplines;
+        uint16_t rxbuf;
+        uint32_t config[2];
+        uint32_t time[4];
+        uint32_t data[6];
+        uint16_t vsync;
+        uint16_t hsync;
+        struct rfbi_chip_s *chip[2];
+    } rfbi;
+};
+
+static void omap_dispc_interrupt_update(struct omap_dss_s *s)
+{
+    qemu_set_irq(s->irq, s->dispc.irqst & s->dispc.irqen);
+}
+
+static void omap_rfbi_reset(struct omap_dss_s *s)
+{
+    s->rfbi.idlemode = 0;
+    s->rfbi.control = 2;
+    s->rfbi.enable = 0;
+    s->rfbi.pixels = 0;
+    s->rfbi.skiplines = 0;
+    s->rfbi.busy = 0;
+    s->rfbi.config[0] = 0x00310000;
+    s->rfbi.config[1] = 0x00310000;
+    s->rfbi.time[0] = 0;
+    s->rfbi.time[1] = 0;
+    s->rfbi.time[2] = 0;
+    s->rfbi.time[3] = 0;
+    s->rfbi.data[0] = 0;
+    s->rfbi.data[1] = 0;
+    s->rfbi.data[2] = 0;
+    s->rfbi.data[3] = 0;
+    s->rfbi.data[4] = 0;
+    s->rfbi.data[5] = 0;
+    s->rfbi.vsync = 0;
+    s->rfbi.hsync = 0;
+}
+
+void omap_dss_reset(struct omap_dss_s *s)
+{
+    s->autoidle = 0;
+    s->control = 0;
+    s->enable = 0;
+
+    s->dig.enable = 0;
+    s->dig.nx = 1;
+    s->dig.ny = 1;
+
+    s->lcd.enable = 0;
+    s->lcd.nx = 1;
+    s->lcd.ny = 1;
+
+    s->dispc.idlemode = 0;
+    s->dispc.irqst = 0;
+    s->dispc.irqen = 0;
+    s->dispc.control = 0;
+    s->dispc.config = 0;
+    s->dispc.capable = 0x161;
+    s->dispc.timing[0] = 0;
+    s->dispc.timing[1] = 0;
+    s->dispc.timing[2] = 0;
+    s->dispc.line = 0;
+    s->dispc.bg[0] = 0;
+    s->dispc.bg[1] = 0;
+    s->dispc.trans[0] = 0;
+    s->dispc.trans[1] = 0;
+
+    s->dispc.l[0].enable = 0;
+    s->dispc.l[0].bpp = 0;
+    s->dispc.l[0].addr[0] = 0;
+    s->dispc.l[0].addr[1] = 0;
+    s->dispc.l[0].addr[2] = 0;
+    s->dispc.l[0].posx = 0;
+    s->dispc.l[0].posy = 0;
+    s->dispc.l[0].nx = 1;
+    s->dispc.l[0].ny = 1;
+    s->dispc.l[0].attr = 0;
+    s->dispc.l[0].tresh = 0;
+    s->dispc.l[0].rowinc = 1;
+    s->dispc.l[0].colinc = 1;
+    s->dispc.l[0].wininc = 0;
+
+    omap_rfbi_reset(s);
+    omap_dispc_interrupt_update(s);
+}
+
+static uint32_t omap_diss_read(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+    int offset = addr - s->diss_base;
+
+    switch (offset) {
+    case 0x00:	/* DSS_REVISIONNUMBER */
+        return 0x20;
+
+    case 0x10:	/* DSS_SYSCONFIG */
+        return s->autoidle;
+
+    case 0x14:	/* DSS_SYSSTATUS */
+        return 1;						/* RESETDONE */
+
+    case 0x40:	/* DSS_CONTROL */
+        return s->control;
+
+    case 0x50:	/* DSS_PSA_LCD_REG_1 */
+    case 0x54:	/* DSS_PSA_LCD_REG_2 */
+    case 0x58:	/* DSS_PSA_VIDEO_REG */
+        /* TODO: fake some values when appropriate s->control bits are set */
+        return 0;
+
+    case 0x5c:	/* DSS_STATUS */
+        return 1 + (s->control & 1);
+
+    default:
+        break;
+    }
+    OMAP_BAD_REG(addr);
+    return 0;
+}
+
+static void omap_diss_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+    int offset = addr - s->diss_base;
+
+    switch (offset) {
+    case 0x00:	/* DSS_REVISIONNUMBER */
+    case 0x14:	/* DSS_SYSSTATUS */
+    case 0x50:	/* DSS_PSA_LCD_REG_1 */
+    case 0x54:	/* DSS_PSA_LCD_REG_2 */
+    case 0x58:	/* DSS_PSA_VIDEO_REG */
+    case 0x5c:	/* DSS_STATUS */
+        OMAP_RO_REG(addr);
+        break;
+
+    case 0x10:	/* DSS_SYSCONFIG */
+        if (value & 2)						/* SOFTRESET */
+            omap_dss_reset(s);
+        s->autoidle = value & 1;
+        break;
+
+    case 0x40:	/* DSS_CONTROL */
+        s->control = value & 0x3dd;
+        break;
+
+    default:
+        OMAP_BAD_REG(addr);
+    }
+}
+
+static CPUReadMemoryFunc *omap_diss1_readfn[] = {
+    omap_badwidth_read32,
+    omap_badwidth_read32,
+    omap_diss_read,
+};
+
+static CPUWriteMemoryFunc *omap_diss1_writefn[] = {
+    omap_badwidth_write32,
+    omap_badwidth_write32,
+    omap_diss_write,
+};
+
+static uint32_t omap_disc_read(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+    int offset = addr - s->disc_base;
+
+    switch (offset) {
+    case 0x000:	/* DISPC_REVISION */
+        return 0x20;
+
+    case 0x010:	/* DISPC_SYSCONFIG */
+        return s->dispc.idlemode;
+
+    case 0x014:	/* DISPC_SYSSTATUS */
+        return 1;						/* RESETDONE */
+
+    case 0x018:	/* DISPC_IRQSTATUS */
+        return s->dispc.irqst;
+
+    case 0x01c:	/* DISPC_IRQENABLE */
+        return s->dispc.irqen;
+
+    case 0x040:	/* DISPC_CONTROL */
+        return s->dispc.control;
+
+    case 0x044:	/* DISPC_CONFIG */
+        return s->dispc.config;
+
+    case 0x048:	/* DISPC_CAPABLE */
+        return s->dispc.capable;
+
+    case 0x04c:	/* DISPC_DEFAULT_COLOR0 */
+        return s->dispc.bg[0];
+    case 0x050:	/* DISPC_DEFAULT_COLOR1 */
+        return s->dispc.bg[1];
+    case 0x054:	/* DISPC_TRANS_COLOR0 */
+        return s->dispc.trans[0];
+    case 0x058:	/* DISPC_TRANS_COLOR1 */
+        return s->dispc.trans[1];
+
+    case 0x05c:	/* DISPC_LINE_STATUS */
+        return 0x7ff;
+    case 0x060:	/* DISPC_LINE_NUMBER */
+        return s->dispc.line;
+
+    case 0x064:	/* DISPC_TIMING_H */
+        return s->dispc.timing[0];
+    case 0x068:	/* DISPC_TIMING_V */
+        return s->dispc.timing[1];
+    case 0x06c:	/* DISPC_POL_FREQ */
+        return s->dispc.timing[2];
+    case 0x070:	/* DISPC_DIVISOR */
+        return s->dispc.timing[3];
+
+    case 0x078:	/* DISPC_SIZE_DIG */
+        return ((s->dig.ny - 1) << 16) | (s->dig.nx - 1);
+    case 0x07c:	/* DISPC_SIZE_LCD */
+        return ((s->lcd.ny - 1) << 16) | (s->lcd.nx - 1);
+
+    case 0x080:	/* DISPC_GFX_BA0 */
+        return s->dispc.l[0].addr[0];
+    case 0x084:	/* DISPC_GFX_BA1 */
+        return s->dispc.l[0].addr[1];
+    case 0x088:	/* DISPC_GFX_POSITION */
+        return (s->dispc.l[0].posy << 16) | s->dispc.l[0].posx;
+    case 0x08c:	/* DISPC_GFX_SIZE */
+        return ((s->dispc.l[0].ny - 1) << 16) | (s->dispc.l[0].nx - 1);
+    case 0x0a0:	/* DISPC_GFX_ATTRIBUTES */
+        return s->dispc.l[0].attr;
+    case 0x0a4:	/* DISPC_GFX_FIFO_TRESHOLD */
+        return s->dispc.l[0].tresh;
+    case 0x0a8:	/* DISPC_GFX_FIFO_SIZE_STATUS */
+        return 256;
+    case 0x0ac:	/* DISPC_GFX_ROW_INC */
+        return s->dispc.l[0].rowinc;
+    case 0x0b0:	/* DISPC_GFX_PIXEL_INC */
+        return s->dispc.l[0].colinc;
+    case 0x0b4:	/* DISPC_GFX_WINDOW_SKIP */
+        return s->dispc.l[0].wininc;
+    case 0x0b8:	/* DISPC_GFX_TABLE_BA */
+        return s->dispc.l[0].addr[2];
+
+    case 0x0bc:	/* DISPC_VID1_BA0 */
+    case 0x0c0:	/* DISPC_VID1_BA1 */
+    case 0x0c4:	/* DISPC_VID1_POSITION */
+    case 0x0c8:	/* DISPC_VID1_SIZE */
+    case 0x0cc:	/* DISPC_VID1_ATTRIBUTES */
+    case 0x0d0:	/* DISPC_VID1_FIFO_TRESHOLD */
+    case 0x0d4:	/* DISPC_VID1_FIFO_SIZE_STATUS */
+    case 0x0d8:	/* DISPC_VID1_ROW_INC */
+    case 0x0dc:	/* DISPC_VID1_PIXEL_INC */
+    case 0x0e0:	/* DISPC_VID1_FIR */
+    case 0x0e4:	/* DISPC_VID1_PICTURE_SIZE */
+    case 0x0e8:	/* DISPC_VID1_ACCU0 */
+    case 0x0ec:	/* DISPC_VID1_ACCU1 */
+    case 0x0f0 ... 0x140:	/* DISPC_VID1_FIR_COEF, DISPC_VID1_CONV_COEF */
+    case 0x14c:	/* DISPC_VID2_BA0 */
+    case 0x150:	/* DISPC_VID2_BA1 */
+    case 0x154:	/* DISPC_VID2_POSITION */
+    case 0x158:	/* DISPC_VID2_SIZE */
+    case 0x15c:	/* DISPC_VID2_ATTRIBUTES */
+    case 0x160:	/* DISPC_VID2_FIFO_TRESHOLD */
+    case 0x164:	/* DISPC_VID2_FIFO_SIZE_STATUS */
+    case 0x168:	/* DISPC_VID2_ROW_INC */
+    case 0x16c:	/* DISPC_VID2_PIXEL_INC */
+    case 0x170:	/* DISPC_VID2_FIR */
+    case 0x174:	/* DISPC_VID2_PICTURE_SIZE */
+    case 0x178:	/* DISPC_VID2_ACCU0 */
+    case 0x17c:	/* DISPC_VID2_ACCU1 */
+    case 0x180 ... 0x1d0:	/* DISPC_VID2_FIR_COEF, DISPC_VID2_CONV_COEF */
+    case 0x1d4:	/* DISPC_DATA_CYCLE1 */
+    case 0x1d8:	/* DISPC_DATA_CYCLE2 */
+    case 0x1dc:	/* DISPC_DATA_CYCLE3 */
+        return 0;
+
+    default:
+        break;
+    }
+    OMAP_BAD_REG(addr);
+    return 0;
+}
+
+static void omap_disc_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+    int offset = addr - s->disc_base;
+
+    switch (offset) {
+    case 0x010:	/* DISPC_SYSCONFIG */
+        if (value & 2)						/* SOFTRESET */
+            omap_dss_reset(s);
+        s->dispc.idlemode = value & 0x301b;
+        break;
+
+    case 0x018:	/* DISPC_IRQSTATUS */
+        s->dispc.irqst &= ~value;
+        omap_dispc_interrupt_update(s);
+        break;
+
+    case 0x01c:	/* DISPC_IRQENABLE */
+        s->dispc.irqen = value & 0xffff;
+        omap_dispc_interrupt_update(s);
+        break;
+
+    case 0x040:	/* DISPC_CONTROL */
+        s->dispc.control = value & 0x07ff9fff;
+        s->dig.enable = (value >> 1) & 1;
+        s->lcd.enable = (value >> 0) & 1;
+        if (value & (1 << 12))			/* OVERLAY_OPTIMIZATION */
+            if (~((s->dispc.l[1].attr | s->dispc.l[2].attr) & 1))
+                 fprintf(stderr, "%s: Overlay Optimization when no overlay "
+                                 "region effectively exists leads to "
+                                 "unpredictable behaviour!\n", __FUNCTION__);
+        if (value & (1 << 6)) {				/* GODIGITAL */
+            //// Shadows:
+            //// s->dispc.config
+            //// s->dispc.capable
+            //// s->dispc.bg[0]
+            //// s->dispc.bg[1]
+            //// s->dispc.trans[0]
+            //// s->dispc.trans[1]
+            //// s->dispc.line
+            //// s->dispc.timing[0]
+            //// s->dispc.timing[1]
+            //// s->dispc.timing[2]
+            //// s->dispc.timing[3]
+            //// s->lcd.nx
+            //// s->lcd.ny
+            //// s->dig.nx
+            //// s->dig.ny
+            //// s->dispc.l[0].addr[0]
+            //// s->dispc.l[0].addr[1]
+            //// s->dispc.l[0].addr[2]
+            //// s->dispc.l[0].posx
+            //// s->dispc.l[0].posy
+            //// s->dispc.l[0].nx
+            //// s->dispc.l[0].ny
+            //// s->dispc.l[0].tresh
+            //// s->dispc.l[0].rowinc
+            //// s->dispc.l[0].colinc
+            //// s->dispc.l[0].wininc
+        }
+        if (value & (1 << 5)) {				/* GOLCD */
+        }
+        s->dispc.invalidate = 1;
+        break;
+
+    case 0x044:	/* DISPC_CONFIG */
+        s->dispc.config = value & 0x3fff;
+        //// bits 2:1 (LOADMODE) reset to 0 after set to 1 and palette loaded
+        //// bits 2:1 (LOADMODE) reset to 2 after set to 3 and palette loaded
+        s->dispc.invalidate = 1;
+        break;
+
+    case 0x048:	/* DISPC_CAPABLE */
+        s->dispc.capable = value & 0x3ff;
+        break;
+
+    case 0x04c:	/* DISPC_DEFAULT_COLOR0 */
+        s->dispc.bg[0] = value & 0xffffff;
+        s->dispc.invalidate = 1;
+        break;
+    case 0x050:	/* DISPC_DEFAULT_COLOR1 */
+        s->dispc.bg[1] = value & 0xffffff;
+        s->dispc.invalidate = 1;
+        break;
+    case 0x054:	/* DISPC_TRANS_COLOR0 */
+        s->dispc.trans[0] = value & 0xffffff;
+        s->dispc.invalidate = 1;
+        break;
+    case 0x058:	/* DISPC_TRANS_COLOR1 */
+        s->dispc.trans[1] = value & 0xffffff;
+        s->dispc.invalidate = 1;
+        break;
+
+    case 0x060:	/* DISPC_LINE_NUMBER */
+        s->dispc.line = value & 0x7ff;
+        break;
+
+    case 0x064:	/* DISPC_TIMING_H */
+        s->dispc.timing[0] = value & 0x0ff0ff3f;
+        break;
+    case 0x068:	/* DISPC_TIMING_V */
+        s->dispc.timing[1] = value & 0x0ff0ff3f;
+        break;
+    case 0x06c:	/* DISPC_POL_FREQ */
+        s->dispc.timing[2] = value & 0x0003ffff;
+        break;
+    case 0x070:	/* DISPC_DIVISOR */
+        s->dispc.timing[3] = value & 0x00ff00ff;
+        break;
+
+    case 0x078:	/* DISPC_SIZE_DIG */
+        s->dig.nx = ((value >>  0) & 0x7ff) + 1;		/* PPL */
+        s->dig.ny = ((value >> 16) & 0x7ff) + 1;		/* LPP */
+        s->dispc.invalidate = 1;
+        break;
+    case 0x07c:	/* DISPC_SIZE_LCD */
+        s->lcd.nx = ((value >>  0) & 0x7ff) + 1;		/* PPL */
+        s->lcd.ny = ((value >> 16) & 0x7ff) + 1;		/* LPP */
+        s->dispc.invalidate = 1;
+        break;
+    case 0x080:	/* DISPC_GFX_BA0 */
+        s->dispc.l[0].addr[0] = (target_phys_addr_t) value;
+        s->dispc.invalidate = 1;
+        break;
+    case 0x084:	/* DISPC_GFX_BA1 */
+        s->dispc.l[0].addr[1] = (target_phys_addr_t) value;
+        s->dispc.invalidate = 1;
+        break;
+    case 0x088:	/* DISPC_GFX_POSITION */
+        s->dispc.l[0].posx = ((value >>  0) & 0x7ff);		/* GFXPOSX */
+        s->dispc.l[0].posy = ((value >> 16) & 0x7ff);		/* GFXPOSY */
+        s->dispc.invalidate = 1;
+        break;
+    case 0x08c:	/* DISPC_GFX_SIZE */
+        s->dispc.l[0].nx = ((value >>  0) & 0x7ff) + 1;		/* GFXSIZEX */
+        s->dispc.l[0].ny = ((value >> 16) & 0x7ff) + 1;		/* GFXSIZEY */
+        s->dispc.invalidate = 1;
+        break;
+    case 0x0a0:	/* DISPC_GFX_ATTRIBUTES */
+        s->dispc.l[0].attr = value & 0x7ff;
+        if (value & (3 << 9))
+            fprintf(stderr, "%s: Big-endian pixel format not supported\n",
+                            __FUNCTION__);
+        s->dispc.l[0].enable = value & 1;
+        s->dispc.l[0].bpp = (value >> 1) & 0xf;
+        s->dispc.invalidate = 1;
+        break;
+    case 0x0a4:	/* DISPC_GFX_FIFO_TRESHOLD */
+        s->dispc.l[0].tresh = value & 0x01ff01ff;
+        break;
+    case 0x0ac:	/* DISPC_GFX_ROW_INC */
+        s->dispc.l[0].rowinc = value;
+        s->dispc.invalidate = 1;
+        break;
+    case 0x0b0:	/* DISPC_GFX_PIXEL_INC */
+        s->dispc.l[0].colinc = value;
+        s->dispc.invalidate = 1;
+        break;
+    case 0x0b4:	/* DISPC_GFX_WINDOW_SKIP */
+        s->dispc.l[0].wininc = value;
+        break;
+    case 0x0b8:	/* DISPC_GFX_TABLE_BA */
+        s->dispc.l[0].addr[2] = (target_phys_addr_t) value;
+        s->dispc.invalidate = 1;
+        break;
+
+    case 0x0bc:	/* DISPC_VID1_BA0 */
+    case 0x0c0:	/* DISPC_VID1_BA1 */
+    case 0x0c4:	/* DISPC_VID1_POSITION */
+    case 0x0c8:	/* DISPC_VID1_SIZE */
+    case 0x0cc:	/* DISPC_VID1_ATTRIBUTES */
+    case 0x0d0:	/* DISPC_VID1_FIFO_TRESHOLD */
+    case 0x0d8:	/* DISPC_VID1_ROW_INC */
+    case 0x0dc:	/* DISPC_VID1_PIXEL_INC */
+    case 0x0e0:	/* DISPC_VID1_FIR */
+    case 0x0e4:	/* DISPC_VID1_PICTURE_SIZE */
+    case 0x0e8:	/* DISPC_VID1_ACCU0 */
+    case 0x0ec:	/* DISPC_VID1_ACCU1 */
+    case 0x0f0 ... 0x140:	/* DISPC_VID1_FIR_COEF, DISPC_VID1_CONV_COEF */
+    case 0x14c:	/* DISPC_VID2_BA0 */
+    case 0x150:	/* DISPC_VID2_BA1 */
+    case 0x154:	/* DISPC_VID2_POSITION */
+    case 0x158:	/* DISPC_VID2_SIZE */
+    case 0x15c:	/* DISPC_VID2_ATTRIBUTES */
+    case 0x160:	/* DISPC_VID2_FIFO_TRESHOLD */
+    case 0x168:	/* DISPC_VID2_ROW_INC */
+    case 0x16c:	/* DISPC_VID2_PIXEL_INC */
+    case 0x170:	/* DISPC_VID2_FIR */
+    case 0x174:	/* DISPC_VID2_PICTURE_SIZE */
+    case 0x178:	/* DISPC_VID2_ACCU0 */
+    case 0x17c:	/* DISPC_VID2_ACCU1 */
+    case 0x180 ... 0x1d0:	/* DISPC_VID2_FIR_COEF, DISPC_VID2_CONV_COEF */
+    case 0x1d4:	/* DISPC_DATA_CYCLE1 */
+    case 0x1d8:	/* DISPC_DATA_CYCLE2 */
+    case 0x1dc:	/* DISPC_DATA_CYCLE3 */
+        break;
+
+    default:
+        OMAP_BAD_REG(addr);
+    }
+}
+
+static CPUReadMemoryFunc *omap_disc1_readfn[] = {
+    omap_badwidth_read32,
+    omap_badwidth_read32,
+    omap_disc_read,
+};
+
+static CPUWriteMemoryFunc *omap_disc1_writefn[] = {
+    omap_badwidth_write32,
+    omap_badwidth_write32,
+    omap_disc_write,
+};
+
+static void *omap_rfbi_get_buffer(struct omap_dss_s *s)
+{
+    target_phys_addr_t fb;
+    uint32_t pd;
+
+    /* TODO */
+    fb = s->dispc.l[0].addr[0];
+
+    pd = cpu_get_physical_page_desc(fb);
+    if ((pd & ~TARGET_PAGE_MASK) != IO_MEM_RAM)
+        /* TODO */
+        cpu_abort(cpu_single_env, "%s: framebuffer outside RAM!\n",
+                        __FUNCTION__);
+    else
+        return phys_ram_base +
+                (pd & TARGET_PAGE_MASK) +
+                (fb & ~TARGET_PAGE_MASK);
+}
+
+static void omap_rfbi_transfer_stop(struct omap_dss_s *s)
+{
+    if (!s->rfbi.busy)
+        return;
+
+    /* TODO: in non-Bypass mode we probably need to just deassert the DRQ.  */
+
+    s->rfbi.busy = 0;
+}
+
+static void omap_rfbi_transfer_start(struct omap_dss_s *s)
+{
+    void *data;
+    size_t len;
+    int pitch;
+
+    if (!s->rfbi.enable || s->rfbi.busy)
+        return;
+
+    if (s->rfbi.control & (1 << 1)) {				/* BYPASS */
+        /* TODO: in non-Bypass mode we probably need to just assert the
+         * DRQ and wait for DMA to write the pixels.  */
+        fprintf(stderr, "%s: Bypass mode unimplemented\n", __FUNCTION__);
+        return;
+    }
+
+    if (!(s->dispc.control & (1 << 11)))			/* RFBIMODE */
+        return;
+    /* TODO: check that LCD output is enabled in DISPC.  */
+
+    s->rfbi.busy = 1;
+
+    data = omap_rfbi_get_buffer(s);
+
+    /* TODO bpp */
+    len = s->rfbi.pixels * 2;
+    s->rfbi.pixels = 0;
+
+    /* TODO: negative values */
+    pitch = s->dispc.l[0].nx + (s->dispc.l[0].rowinc - 1) / 2;
+
+    if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0])
+        s->rfbi.chip[0]->block(s->rfbi.chip[0]->opaque, 1, data, len, pitch);
+    if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1])
+        s->rfbi.chip[1]->block(s->rfbi.chip[1]->opaque, 1, data, len, pitch);
+
+    omap_rfbi_transfer_stop(s);
+
+    /* TODO */
+    s->dispc.irqst |= 1;					/* FRAMEDONE */
+    omap_dispc_interrupt_update(s);
+}
+
+static uint32_t omap_rfbi_read(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+    int offset = addr - s->rfbi_base;
+
+    switch (offset) {
+    case 0x00:	/* RFBI_REVISION */
+        return 0x10;
+
+    case 0x10:	/* RFBI_SYSCONFIG */
+        return s->rfbi.idlemode;
+
+    case 0x14:	/* RFBI_SYSSTATUS */
+        return 1 | (s->rfbi.busy << 8);				/* RESETDONE */
+
+    case 0x40:	/* RFBI_CONTROL */
+        return s->rfbi.control;
+
+    case 0x44:	/* RFBI_PIXELCNT */
+        return s->rfbi.pixels;
+
+    case 0x48:	/* RFBI_LINE_NUMBER */
+        return s->rfbi.skiplines;
+
+    case 0x58:	/* RFBI_READ */
+    case 0x5c:	/* RFBI_STATUS */
+        return s->rfbi.rxbuf;
+
+    case 0x60:	/* RFBI_CONFIG0 */
+        return s->rfbi.config[0];
+    case 0x64:	/* RFBI_ONOFF_TIME0 */
+        return s->rfbi.time[0];
+    case 0x68:	/* RFBI_CYCLE_TIME0 */
+        return s->rfbi.time[1];
+    case 0x6c:	/* RFBI_DATA_CYCLE1_0 */
+        return s->rfbi.data[0];
+    case 0x70:	/* RFBI_DATA_CYCLE2_0 */
+        return s->rfbi.data[1];
+    case 0x74:	/* RFBI_DATA_CYCLE3_0 */
+        return s->rfbi.data[2];
+
+    case 0x78:	/* RFBI_CONFIG1 */
+        return s->rfbi.config[1];
+    case 0x7c:	/* RFBI_ONOFF_TIME1 */
+        return s->rfbi.time[2];
+    case 0x80:	/* RFBI_CYCLE_TIME1 */
+        return s->rfbi.time[3];
+    case 0x84:	/* RFBI_DATA_CYCLE1_1 */
+        return s->rfbi.data[3];
+    case 0x88:	/* RFBI_DATA_CYCLE2_1 */
+        return s->rfbi.data[4];
+    case 0x8c:	/* RFBI_DATA_CYCLE3_1 */
+        return s->rfbi.data[5];
+
+    case 0x90:	/* RFBI_VSYNC_WIDTH */
+        return s->rfbi.vsync;
+    case 0x94:	/* RFBI_HSYNC_WIDTH */
+        return s->rfbi.hsync;
+    }
+    OMAP_BAD_REG(addr);
+    return 0;
+}
+
+static void omap_rfbi_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+    int offset = addr - s->rfbi_base;
+
+    switch (offset) {
+    case 0x10:	/* RFBI_SYSCONFIG */
+        if (value & 2)						/* SOFTRESET */
+            omap_rfbi_reset(s);
+        s->rfbi.idlemode = value & 0x19;
+        break;
+
+    case 0x40:	/* RFBI_CONTROL */
+        s->rfbi.control = value & 0xf;
+        s->rfbi.enable = value & 1;
+        if (value & (1 << 4) &&					/* ITE */
+                        !(s->rfbi.config[0] & s->rfbi.config[1] & 0xc))
+            omap_rfbi_transfer_start(s);
+        break;
+
+    case 0x44:	/* RFBI_PIXELCNT */
+        s->rfbi.pixels = value;
+        break;
+
+    case 0x48:	/* RFBI_LINE_NUMBER */
+        s->rfbi.skiplines = value & 0x7ff;
+        break;
+
+    case 0x4c:	/* RFBI_CMD */
+        if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0])
+            s->rfbi.chip[0]->write(s->rfbi.chip[0]->opaque, 0, value & 0xffff);
+        if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1])
+            s->rfbi.chip[1]->write(s->rfbi.chip[1]->opaque, 0, value & 0xffff);
+        break;
+    case 0x50:	/* RFBI_PARAM */
+        if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0])
+            s->rfbi.chip[0]->write(s->rfbi.chip[0]->opaque, 1, value & 0xffff);
+        if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1])
+            s->rfbi.chip[1]->write(s->rfbi.chip[1]->opaque, 1, value & 0xffff);
+        break;
+    case 0x54:	/* RFBI_DATA */
+        /* TODO: take into account the format set up in s->rfbi.config[?] and
+         * s->rfbi.data[?], but special-case the most usual scenario so that
+         * speed doesn't suffer.  */
+        if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0]) {
+            s->rfbi.chip[0]->write(s->rfbi.chip[0]->opaque, 1, value & 0xffff);
+            s->rfbi.chip[0]->write(s->rfbi.chip[0]->opaque, 1, value >> 16);
+        }
+        if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1]) {
+            s->rfbi.chip[1]->write(s->rfbi.chip[1]->opaque, 1, value & 0xffff);
+            s->rfbi.chip[1]->write(s->rfbi.chip[1]->opaque, 1, value >> 16);
+        }
+        if (!-- s->rfbi.pixels)
+            omap_rfbi_transfer_stop(s);
+        break;
+    case 0x58:	/* RFBI_READ */
+        if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0])
+            s->rfbi.rxbuf = s->rfbi.chip[0]->read(s->rfbi.chip[0]->opaque, 1);
+        else if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1])
+            s->rfbi.rxbuf = s->rfbi.chip[0]->read(s->rfbi.chip[0]->opaque, 1);
+        if (!-- s->rfbi.pixels)
+            omap_rfbi_transfer_stop(s);
+        break;
+
+    case 0x5c:	/* RFBI_STATUS */
+        if ((s->rfbi.control & (1 << 2)) && s->rfbi.chip[0])
+            s->rfbi.rxbuf = s->rfbi.chip[0]->read(s->rfbi.chip[0]->opaque, 0);
+        else if ((s->rfbi.control & (1 << 3)) && s->rfbi.chip[1])
+            s->rfbi.rxbuf = s->rfbi.chip[0]->read(s->rfbi.chip[0]->opaque, 0);
+        if (!-- s->rfbi.pixels)
+            omap_rfbi_transfer_stop(s);
+        break;
+
+    case 0x60:	/* RFBI_CONFIG0 */
+        s->rfbi.config[0] = value & 0x003f1fff;
+        break;
+
+    case 0x64:	/* RFBI_ONOFF_TIME0 */
+        s->rfbi.time[0] = value & 0x3fffffff;
+        break;
+    case 0x68:	/* RFBI_CYCLE_TIME0 */
+        s->rfbi.time[1] = value & 0x0fffffff;
+        break;
+    case 0x6c:	/* RFBI_DATA_CYCLE1_0 */
+        s->rfbi.data[0] = value & 0x0f1f0f1f;
+        break;
+    case 0x70:	/* RFBI_DATA_CYCLE2_0 */
+        s->rfbi.data[1] = value & 0x0f1f0f1f;
+        break;
+    case 0x74:	/* RFBI_DATA_CYCLE3_0 */
+        s->rfbi.data[2] = value & 0x0f1f0f1f;
+        break;
+    case 0x78:	/* RFBI_CONFIG1 */
+        s->rfbi.config[1] = value & 0x003f1fff;
+        break;
+
+    case 0x7c:	/* RFBI_ONOFF_TIME1 */
+        s->rfbi.time[2] = value & 0x3fffffff;
+        break;
+    case 0x80:	/* RFBI_CYCLE_TIME1 */
+        s->rfbi.time[3] = value & 0x0fffffff;
+        break;
+    case 0x84:	/* RFBI_DATA_CYCLE1_1 */
+        s->rfbi.data[3] = value & 0x0f1f0f1f;
+        break;
+    case 0x88:	/* RFBI_DATA_CYCLE2_1 */
+        s->rfbi.data[4] = value & 0x0f1f0f1f;
+        break;
+    case 0x8c:	/* RFBI_DATA_CYCLE3_1 */
+        s->rfbi.data[5] = value & 0x0f1f0f1f;
+        break;
+
+    case 0x90:	/* RFBI_VSYNC_WIDTH */
+        s->rfbi.vsync = value & 0xffff;
+        break;
+    case 0x94:	/* RFBI_HSYNC_WIDTH */
+        s->rfbi.hsync = value & 0xffff;
+        break;
+
+    default:
+        OMAP_BAD_REG(addr);
+    }
+}
+
+static CPUReadMemoryFunc *omap_rfbi1_readfn[] = {
+    omap_badwidth_read32,
+    omap_badwidth_read32,
+    omap_rfbi_read,
+};
+
+static CPUWriteMemoryFunc *omap_rfbi1_writefn[] = {
+    omap_badwidth_write32,
+    omap_badwidth_write32,
+    omap_rfbi_write,
+};
+
+static uint32_t omap_venc_read(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+    int offset = addr - s->venc_base;
+
+    switch (offset) {
+    case 0x00:	/* REV_ID */
+    case 0x04:	/* STATUS */
+    case 0x08:	/* F_CONTROL */
+    case 0x10:	/* VIDOUT_CTRL */
+    case 0x14:	/* SYNC_CTRL */
+    case 0x1c:	/* LLEN */
+    case 0x20:	/* FLENS */
+    case 0x24:	/* HFLTR_CTRL */
+    case 0x28:	/* CC_CARR_WSS_CARR */
+    case 0x2c:	/* C_PHASE */
+    case 0x30:	/* GAIN_U */
+    case 0x34:	/* GAIN_V */
+    case 0x38:	/* GAIN_Y */
+    case 0x3c:	/* BLACK_LEVEL */
+    case 0x40:	/* BLANK_LEVEL */
+    case 0x44:	/* X_COLOR */
+    case 0x48:	/* M_CONTROL */
+    case 0x4c:	/* BSTAMP_WSS_DATA */
+    case 0x50:	/* S_CARR */
+    case 0x54:	/* LINE21 */
+    case 0x58:	/* LN_SEL */
+    case 0x5c:	/* L21__WC_CTL */
+    case 0x60:	/* HTRIGGER_VTRIGGER */
+    case 0x64:	/* SAVID__EAVID */
+    case 0x68:	/* FLEN__FAL */
+    case 0x6c:	/* LAL__PHASE_RESET */
+    case 0x70:	/* HS_INT_START_STOP_X */
+    case 0x74:	/* HS_EXT_START_STOP_X */
+    case 0x78:	/* VS_INT_START_X */
+    case 0x7c:	/* VS_INT_STOP_X__VS_INT_START_Y */
+    case 0x80:	/* VS_INT_STOP_Y__VS_INT_START_X */
+    case 0x84:	/* VS_EXT_STOP_X__VS_EXT_START_Y */
+    case 0x88:	/* VS_EXT_STOP_Y */
+    case 0x90:	/* AVID_START_STOP_X */
+    case 0x94:	/* AVID_START_STOP_Y */
+    case 0xa0:	/* FID_INT_START_X__FID_INT_START_Y */
+    case 0xa4:	/* FID_INT_OFFSET_Y__FID_EXT_START_X */
+    case 0xa8:	/* FID_EXT_START_Y__FID_EXT_OFFSET_Y */
+    case 0xb0:	/* TVDETGP_INT_START_STOP_X */
+    case 0xb4:	/* TVDETGP_INT_START_STOP_Y */
+    case 0xb8:	/* GEN_CTRL */
+    case 0xc4:	/* DAC_TST__DAC_A */
+    case 0xc8:	/* DAC_B__DAC_C */
+        return 0;
+
+    default:
+        break;
+    }
+    OMAP_BAD_REG(addr);
+    return 0;
+}
+
+static void omap_venc_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+    int offset = addr - s->venc_base;
+
+    switch (offset) {
+    case 0x08:	/* F_CONTROL */
+    case 0x10:	/* VIDOUT_CTRL */
+    case 0x14:	/* SYNC_CTRL */
+    case 0x1c:	/* LLEN */
+    case 0x20:	/* FLENS */
+    case 0x24:	/* HFLTR_CTRL */
+    case 0x28:	/* CC_CARR_WSS_CARR */
+    case 0x2c:	/* C_PHASE */
+    case 0x30:	/* GAIN_U */
+    case 0x34:	/* GAIN_V */
+    case 0x38:	/* GAIN_Y */
+    case 0x3c:	/* BLACK_LEVEL */
+    case 0x40:	/* BLANK_LEVEL */
+    case 0x44:	/* X_COLOR */
+    case 0x48:	/* M_CONTROL */
+    case 0x4c:	/* BSTAMP_WSS_DATA */
+    case 0x50:	/* S_CARR */
+    case 0x54:	/* LINE21 */
+    case 0x58:	/* LN_SEL */
+    case 0x5c:	/* L21__WC_CTL */
+    case 0x60:	/* HTRIGGER_VTRIGGER */
+    case 0x64:	/* SAVID__EAVID */
+    case 0x68:	/* FLEN__FAL */
+    case 0x6c:	/* LAL__PHASE_RESET */
+    case 0x70:	/* HS_INT_START_STOP_X */
+    case 0x74:	/* HS_EXT_START_STOP_X */
+    case 0x78:	/* VS_INT_START_X */
+    case 0x7c:	/* VS_INT_STOP_X__VS_INT_START_Y */
+    case 0x80:	/* VS_INT_STOP_Y__VS_INT_START_X */
+    case 0x84:	/* VS_EXT_STOP_X__VS_EXT_START_Y */
+    case 0x88:	/* VS_EXT_STOP_Y */
+    case 0x90:	/* AVID_START_STOP_X */
+    case 0x94:	/* AVID_START_STOP_Y */
+    case 0xa0:	/* FID_INT_START_X__FID_INT_START_Y */
+    case 0xa4:	/* FID_INT_OFFSET_Y__FID_EXT_START_X */
+    case 0xa8:	/* FID_EXT_START_Y__FID_EXT_OFFSET_Y */
+    case 0xb0:	/* TVDETGP_INT_START_STOP_X */
+    case 0xb4:	/* TVDETGP_INT_START_STOP_Y */
+    case 0xb8:	/* GEN_CTRL */
+    case 0xc4:	/* DAC_TST__DAC_A */
+    case 0xc8:	/* DAC_B__DAC_C */
+        break;
+
+    default:
+        OMAP_BAD_REG(addr);
+    }
+}
+
+static CPUReadMemoryFunc *omap_venc1_readfn[] = {
+    omap_badwidth_read32,
+    omap_badwidth_read32,
+    omap_venc_read,
+};
+
+static CPUWriteMemoryFunc *omap_venc1_writefn[] = {
+    omap_badwidth_write32,
+    omap_badwidth_write32,
+    omap_venc_write,
+};
+
+static uint32_t omap_im3_read(void *opaque, target_phys_addr_t addr)
+{
+    struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+    int offset = addr - s->im3_base;
+
+    switch (offset) {
+    case 0x0a8:	/* SBIMERRLOGA */
+    case 0x0b0:	/* SBIMERRLOG */
+    case 0x190:	/* SBIMSTATE */
+    case 0x198:	/* SBTMSTATE_L */
+    case 0x19c:	/* SBTMSTATE_H */
+    case 0x1a8:	/* SBIMCONFIG_L */
+    case 0x1ac:	/* SBIMCONFIG_H */
+    case 0x1f8:	/* SBID_L */
+    case 0x1fc:	/* SBID_H */
+        return 0;
+
+    default:
+        break;
+    }
+    OMAP_BAD_REG(addr);
+    return 0;
+}
+
+static void omap_im3_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct omap_dss_s *s = (struct omap_dss_s *) opaque;
+    int offset = addr - s->im3_base;
+
+    switch (offset) {
+    case 0x0b0:	/* SBIMERRLOG */
+    case 0x190:	/* SBIMSTATE */
+    case 0x198:	/* SBTMSTATE_L */
+    case 0x19c:	/* SBTMSTATE_H */
+    case 0x1a8:	/* SBIMCONFIG_L */
+    case 0x1ac:	/* SBIMCONFIG_H */
+        break;
+
+    default:
+        OMAP_BAD_REG(addr);
+    }
+}
+
+static CPUReadMemoryFunc *omap_im3_readfn[] = {
+    omap_badwidth_read32,
+    omap_badwidth_read32,
+    omap_im3_read,
+};
+
+static CPUWriteMemoryFunc *omap_im3_writefn[] = {
+    omap_badwidth_write32,
+    omap_badwidth_write32,
+    omap_im3_write,
+};
+
+struct omap_dss_s *omap_dss_init(struct omap_target_agent_s *ta,
+                target_phys_addr_t l3_base, DisplayState *ds,
+                qemu_irq irq, qemu_irq drq,
+                omap_clk fck1, omap_clk fck2, omap_clk ck54m,
+                omap_clk ick1, omap_clk ick2)
+{
+    int iomemtype[5];
+    struct omap_dss_s *s = (struct omap_dss_s *)
+            qemu_mallocz(sizeof(struct omap_dss_s));
+
+    s->irq = irq;
+    s->drq = drq;
+    s->state = ds;
+    omap_dss_reset(s);
+
+    iomemtype[0] = cpu_register_io_memory(0, omap_diss1_readfn,
+                    omap_diss1_writefn, s);
+    iomemtype[1] = cpu_register_io_memory(0, omap_disc1_readfn,
+                    omap_disc1_writefn, s);
+    iomemtype[2] = cpu_register_io_memory(0, omap_rfbi1_readfn,
+                    omap_rfbi1_writefn, s);
+    iomemtype[3] = cpu_register_io_memory(0, omap_venc1_readfn,
+                    omap_venc1_writefn, s);
+    iomemtype[4] = cpu_register_io_memory(0, omap_im3_readfn,
+                    omap_im3_writefn, s);
+    s->diss_base = omap_l4_attach(ta, 0, iomemtype[0]);
+    s->disc_base = omap_l4_attach(ta, 1, iomemtype[1]);
+    s->rfbi_base = omap_l4_attach(ta, 2, iomemtype[2]);
+    s->venc_base = omap_l4_attach(ta, 3, iomemtype[3]);
+    s->im3_base = l3_base;
+    cpu_register_physical_memory(s->im3_base, 0x1000, iomemtype[4]);
+
+#if 0
+    if (ds)
+        graphic_console_init(ds, omap_update_display,
+                        omap_invalidate_display, omap_screen_dump, s);
+#endif
+
+    return s;
+}
+
+void omap_rfbi_attach(struct omap_dss_s *s, int cs, struct rfbi_chip_s *chip)
+{
+    if (cs < 0 || cs > 1)
+        cpu_abort(cpu_single_env, "%s: wrong CS %i\n", __FUNCTION__, cs);
+    s->rfbi.chip[cs] = chip;
+}
diff --git a/hw/omap_i2c.c b/hw/omap_i2c.c
index de63309..9915676 100644
--- a/hw/omap_i2c.c
+++ b/hw/omap_i2c.c
@@ -150,6 +150,8 @@ static void omap_i2c_fifo_run(struct omap_i2c_s *s)
             }
             if (ack && s->count_cur)
                 s->stat |= 1 << 4;				/* XRDY */
+            else
+                s->stat &= ~(1 << 4);				/* XRDY */
             if (!s->count_cur) {
                 s->stat |= 1 << 2;				/* ARDY */
                 s->control &= ~(1 << 10);			/* MST */
@@ -161,6 +163,8 @@ static void omap_i2c_fifo_run(struct omap_i2c_s *s)
             }
             if (s->rxlen)
                 s->stat |= 1 << 3;				/* RRDY */
+            else
+                s->stat &= ~(1 << 3);				/* RRDY */
         }
         if (!s->count_cur) {
             if ((s->control >> 1) & 1) {			/* STP */
@@ -321,7 +325,8 @@ static void omap_i2c_write(void *opaque, target_phys_addr_t addr,
             return;
         }
 
-        s->stat &= ~(value & 0x3f);
+        /* RRDY and XRDY are reset by hardware. (in all versions???) */
+        s->stat &= ~(value & 0x27);
         omap_i2c_interrupts_update(s);
         break;
 
@@ -376,11 +381,13 @@ static void omap_i2c_write(void *opaque, target_phys_addr_t addr,
             break;
         }
         if ((value & (1 << 15)) && !(value & (1 << 10))) {	/* MST */
-            printf("%s: I^2C slave mode not supported\n", __FUNCTION__);
+            fprintf(stderr, "%s: I^2C slave mode not supported\n",
+                            __FUNCTION__);
             break;
         }
         if ((value & (1 << 15)) && value & (1 << 8)) {		/* XA */
-            printf("%s: 10-bit addressing mode not supported\n", __FUNCTION__);
+            fprintf(stderr, "%s: 10-bit addressing mode not supported\n",
+                            __FUNCTION__);
             break;
         }
         if ((value & (1 << 15)) && value & (1 << 0)) {		/* STT */
@@ -427,7 +434,7 @@ static void omap_i2c_write(void *opaque, target_phys_addr_t addr,
                 omap_i2c_interrupts_update(s);
             }
         if (value & (1 << 15))					/* ST_EN */
-            printf("%s: System Test not supported\n", __FUNCTION__);
+            fprintf(stderr, "%s: System Test not supported\n", __FUNCTION__);
         break;
 
     default:
diff --git a/hw/omap_mmc.c b/hw/omap_mmc.c
index 6fbbb84..e46289a 100644
--- a/hw/omap_mmc.c
+++ b/hw/omap_mmc.c
@@ -26,19 +26,24 @@ struct omap_mmc_s {
     target_phys_addr_t base;
     qemu_irq irq;
     qemu_irq *dma;
+    qemu_irq coverswitch;
     omap_clk clk;
     SDState *card;
     uint16_t last_cmd;
     uint16_t sdio;
     uint16_t rsp[8];
     uint32_t arg;
+    int lines;
     int dw;
     int mode;
     int enable;
+    int be;
+    int rev;
     uint16_t status;
     uint16_t mask;
     uint8_t cto;
     uint16_t dto;
+    int clkdiv;
     uint16_t fifo[32];
     int fifo_start;
     int fifo_len;
@@ -53,6 +58,11 @@ struct omap_mmc_s {
 
     int ddir;
     int transfer;
+
+    int cdet_wakeup;
+    int cdet_enable;
+    int cdet_state;
+    qemu_irq cdet;
 };
 
 static void omap_mmc_interrupts_update(struct omap_mmc_s *s)
@@ -107,6 +117,11 @@ static void omap_mmc_command(struct omap_mmc_s *host, int cmd, int dir,
     struct sd_request_s request;
     uint8_t response[16];
 
+    if (init && cmd == 0) {
+        host->status |= 0x0001;
+        return;
+    }
+
     if (resptype == sd_r1 && busy)
         resptype = sd_r1b;
 
@@ -265,6 +280,34 @@ static void omap_mmc_update(void *opaque)
     omap_mmc_interrupts_update(s);
 }
 
+void omap_mmc_reset(struct omap_mmc_s *host)
+{
+    host->last_cmd = 0;
+    memset(host->rsp, 0, sizeof(host->rsp));
+    host->arg = 0;
+    host->dw = 0;
+    host->mode = 0;
+    host->enable = 0;
+    host->status = 0;
+    host->mask = 0;
+    host->cto = 0;
+    host->dto = 0;
+    host->fifo_len = 0;
+    host->blen = 0;
+    host->blen_counter = 0;
+    host->nblk = 0;
+    host->nblk_counter = 0;
+    host->tx_dma = 0;
+    host->rx_dma = 0;
+    host->ae_level = 0x00;
+    host->af_level = 0x1f;
+    host->transfer = 0;
+    host->cdet_wakeup = 0;
+    host->cdet_enable = 0;
+    qemu_set_irq(host->coverswitch, host->cdet_state);
+    host->clkdiv = 0;
+}
+
 static uint32_t omap_mmc_read(void *opaque, target_phys_addr_t offset)
 {
     uint16_t i;
@@ -282,7 +325,8 @@ static uint32_t omap_mmc_read(void *opaque, target_phys_addr_t offset)
         return s->arg >> 16;
 
     case 0x0c:	/* MMC_CON */
-        return (s->dw << 15) | (s->mode << 12) | (s->enable << 11);
+        return (s->dw << 15) | (s->mode << 12) | (s->enable << 11) | 
+                (s->be << 10) | s->clkdiv;
 
     case 0x10:	/* MMC_STAT */
         return s->status;
@@ -324,12 +368,12 @@ static uint32_t omap_mmc_read(void *opaque, target_phys_addr_t offset)
     case 0x30:	/* MMC_SPI */
         return 0x0000;
     case 0x34:	/* MMC_SDIO */
-        return s->sdio;
+        return (s->cdet_wakeup << 2) | (s->cdet_enable) | s->sdio;
     case 0x38:	/* MMC_SYST */
         return 0x0000;
 
     case 0x3c:	/* MMC_REV */
-        return 0x0001;
+        return s->rev;
 
     case 0x40:	/* MMC_RSP0 */
     case 0x44:	/* MMC_RSP1 */
@@ -340,6 +384,13 @@ static uint32_t omap_mmc_read(void *opaque, target_phys_addr_t offset)
     case 0x58:	/* MMC_RSP6 */
     case 0x5c:	/* MMC_RSP7 */
         return s->rsp[(offset - 0x40) >> 2];
+
+    /* OMAP2-specific */
+    case 0x60:	/* MMC_IOSR */
+    case 0x64:	/* MMC_SYSC */
+        return 0;
+    case 0x68:	/* MMC_SYSS */
+        return 1;						/* RSTD */
     }
 
     OMAP_BAD_REG(offset);
@@ -383,10 +434,16 @@ static void omap_mmc_write(void *opaque, target_phys_addr_t offset,
         s->dw = (value >> 15) & 1;
         s->mode = (value >> 12) & 3;
         s->enable = (value >> 11) & 1;
+        s->be = (value >> 10) & 1;
+        s->clkdiv = (value >> 0) & (s->rev >= 2 ? 0x3ff : 0xff);
         if (s->mode != 0)
             printf("SD mode %i unimplemented!\n", s->mode);
-        if (s->dw != 0)
+        if (s->be != 0)
+            printf("SD FIFO byte sex unimplemented!\n");
+        if (s->dw != 0 && s->lines < 4)
             printf("4-bit SD bus enabled\n");
+        if (!s->enable)
+            omap_mmc_reset(s);
         break;
 
     case 0x10:	/* MMC_STAT */
@@ -395,13 +452,13 @@ static void omap_mmc_write(void *opaque, target_phys_addr_t offset,
         break;
 
     case 0x14:	/* MMC_IE */
-        s->mask = value;
+        s->mask = value & 0x7fff;
         omap_mmc_interrupts_update(s);
         break;
 
     case 0x18:	/* MMC_CTO */
         s->cto = value & 0xff;
-        if (s->cto > 0xfd)
+        if (s->cto > 0xfd && s->rev <= 1)
             printf("MMC: CTO of 0xff and 0xfe cannot be used!\n");
         break;
 
@@ -446,10 +503,12 @@ static void omap_mmc_write(void *opaque, target_phys_addr_t offset,
         break;
 
     /* SPI, SDIO and TEST modes unimplemented */
-    case 0x30:	/* MMC_SPI */
+    case 0x30:	/* MMC_SPI (OMAP1 only) */
         break;
     case 0x34:	/* MMC_SDIO */
-        s->sdio = value & 0x2020;
+        s->sdio = value & (s->rev >= 2 ? 0xfbf3 : 0x2020);
+        s->cdet_wakeup = (value >> 9) & 1;
+        s->cdet_enable = (value >> 2) & 1;
         break;
     case 0x38:	/* MMC_SYST */
         break;
@@ -466,6 +525,19 @@ static void omap_mmc_write(void *opaque, target_phys_addr_t offset,
         OMAP_RO_REG(offset);
         break;
 
+    /* OMAP2-specific */
+    case 0x60:	/* MMC_IOSR */
+        if (value & 0xf)
+            printf("MMC: SDIO bits used!\n");
+        break;
+    case 0x64:	/* MMC_SYSC */
+        if (value & (1 << 2))					/* SRTS */
+            omap_mmc_reset(s);
+        break;
+    case 0x68:	/* MMC_SYSS */
+        OMAP_RO_REG(offset);
+        break;
+
     default:
         OMAP_BAD_REG(offset);
     }
@@ -483,28 +555,21 @@ static CPUWriteMemoryFunc *omap_mmc_writefn[] = {
     omap_badwidth_write16,
 };
 
-void omap_mmc_reset(struct omap_mmc_s *host)
+static void omap_mmc_cover_cb(void *opaque, int line, int level)
 {
-    host->last_cmd = 0;
-    memset(host->rsp, 0, sizeof(host->rsp));
-    host->arg = 0;
-    host->dw = 0;
-    host->mode = 0;
-    host->enable = 0;
-    host->status = 0;
-    host->mask = 0;
-    host->cto = 0;
-    host->dto = 0;
-    host->fifo_len = 0;
-    host->blen = 0;
-    host->blen_counter = 0;
-    host->nblk = 0;
-    host->nblk_counter = 0;
-    host->tx_dma = 0;
-    host->rx_dma = 0;
-    host->ae_level = 0x00;
-    host->af_level = 0x1f;
-    host->transfer = 0;
+    struct omap_mmc_s *host = (struct omap_mmc_s *) opaque;
+
+    if (!host->cdet_state && level) {
+        host->status |= 0x0002;
+        omap_mmc_interrupts_update(host);
+        if (host->cdet_wakeup)
+            /* TODO: Assert wake-up */;
+    }
+
+    if (host->cdet_state != level) {
+        qemu_set_irq(host->coverswitch, level);
+        host->cdet_state = level;
+    }
 }
 
 struct omap_mmc_s *omap_mmc_init(target_phys_addr_t base,
@@ -519,6 +584,10 @@ struct omap_mmc_s *omap_mmc_init(target_phys_addr_t base,
     s->base = base;
     s->dma = dma;
     s->clk = clk;
+    s->lines = 1;	/* TODO: needs to be settable per-board */
+    s->rev = 1;
+
+    omap_mmc_reset(s);
 
     iomemtype = cpu_register_io_memory(0, omap_mmc_readfn,
                     omap_mmc_writefn, s);
@@ -530,7 +599,46 @@ struct omap_mmc_s *omap_mmc_init(target_phys_addr_t base,
     return s;
 }
 
+struct omap_mmc_s *omap2_mmc_init(struct omap_target_agent_s *ta,
+                BlockDriverState *bd, qemu_irq irq, qemu_irq dma[],
+                omap_clk fclk, omap_clk iclk)
+{
+    int iomemtype;
+    struct omap_mmc_s *s = (struct omap_mmc_s *)
+            qemu_mallocz(sizeof(struct omap_mmc_s));
+
+    s->irq = irq;
+    s->dma = dma;
+    s->clk = fclk;
+    s->lines = 4;
+    s->rev = 2;
+
+    omap_mmc_reset(s);
+
+    iomemtype = cpu_register_io_memory(0, omap_mmc_readfn,
+                    omap_mmc_writefn, s);
+    s->base = omap_l4_attach(ta, 0, iomemtype);
+
+    /* Instantiate the storage */
+    s->card = sd_init(bd, 0);
+
+    s->cdet = qemu_allocate_irqs(omap_mmc_cover_cb, s, 1)[0];
+    sd_set_cb(s->card, 0, s->cdet);
+
+    return s;
+}
+
 void omap_mmc_handlers(struct omap_mmc_s *s, qemu_irq ro, qemu_irq cover)
 {
-    sd_set_cb(s->card, ro, cover);
+    if (s->cdet) {
+        sd_set_cb(s->card, ro, s->cdet);
+        s->coverswitch = cover;
+        qemu_set_irq(cover, s->cdet_state);
+    } else
+        sd_set_cb(s->card, ro, cover);
+}
+
+void omap_mmc_enable(struct omap_mmc_s *s, int enable)
+{
+    sd_enable(s->card, enable);
 }
diff --git a/hw/onenand.c b/hw/onenand.c
new file mode 100644
index 0000000..549d392
--- /dev/null
+++ b/hw/onenand.c
@@ -0,0 +1,642 @@
+/*
+ * OneNAND flash memories emulation.
+ *
+ * Copyright (C) 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 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include "qemu-common.h"
+#include "flash.h"
+#include "irq.h"
+#include "sysemu.h"
+#include "block.h"
+
+/* 11 for 2kB-page OneNAND ("2nd generation") and 10 for 1kB-page chips */
+#define PAGE_SHIFT	11
+
+/* Fixed */
+#define BLOCK_SHIFT	(PAGE_SHIFT + 6)
+
+struct onenand_s {
+    uint32_t id;
+    int shift;
+    target_phys_addr_t base;
+    qemu_irq intr;
+    qemu_irq rdy;
+    BlockDriverState *bdrv;
+    BlockDriverState *bdrv_cur;
+    uint8_t *image;
+    uint8_t *otp;
+    uint8_t *current;
+    ram_addr_t ram;
+    uint8_t *boot[2];
+    uint8_t *data[2][2];
+    int iomemtype;
+    int cycle;
+    int otpmode;
+
+    uint16_t addr[8];
+    uint16_t unladdr[8];
+    int bufaddr;
+    int count;
+    uint16_t command;
+    uint16_t config[2];
+    uint16_t status;
+    uint16_t intstatus;
+    uint16_t wpstatus;
+
+    struct ecc_state_s ecc;
+
+    int density_mask;
+    int secs;
+    int secs_cur;
+    int blocks;
+    uint8_t *blockwp;
+};
+
+enum {
+    ONEN_BUF_BLOCK = 0,
+    ONEN_BUF_BLOCK2 = 1,
+    ONEN_BUF_DEST_BLOCK = 2,
+    ONEN_BUF_DEST_PAGE = 3,
+    ONEN_BUF_PAGE = 7,
+};
+
+enum {
+    ONEN_ERR_CMD = 1 << 10,
+    ONEN_ERR_ERASE = 1 << 11,
+    ONEN_ERR_PROG = 1 << 12,
+    ONEN_ERR_LOAD = 1 << 13,
+};
+
+enum {
+    ONEN_INT_RESET = 1 << 4,
+    ONEN_INT_ERASE = 1 << 5,
+    ONEN_INT_PROG = 1 << 6,
+    ONEN_INT_LOAD = 1 << 7,
+    ONEN_INT = 1 << 15,
+};
+
+enum {
+    ONEN_LOCK_LOCKTIGHTEN = 1 << 0,
+    ONEN_LOCK_LOCKED = 1 << 1,
+    ONEN_LOCK_UNLOCKED = 1 << 2,
+};
+
+void onenand_base_update(void *opaque, target_phys_addr_t new)
+{
+    struct onenand_s *s = (struct onenand_s *) opaque;
+
+    s->base = new;
+
+    /* XXX: We should use IO_MEM_ROMD but we broke it earlier...
+     * Both 0x0000 ... 0x01ff and 0x8000 ... 0x800f can be used to
+     * write boot commands.  Also take note of the BWPS bit.  */
+    cpu_register_physical_memory(s->base + (0x0000 << s->shift),
+                    0x0200 << s->shift, s->iomemtype);
+    cpu_register_physical_memory(s->base + (0x0200 << s->shift),
+                    0xbe00 << s->shift,
+                    (s->ram +(0x0200 << s->shift)) | IO_MEM_RAM);
+    if (s->iomemtype)
+        cpu_register_physical_memory(s->base + (0xc000 << s->shift),
+                        0x4000 << s->shift, s->iomemtype);
+}
+
+void onenand_base_unmap(void *opaque)
+{
+    struct onenand_s *s = (struct onenand_s *) opaque;
+
+    cpu_register_physical_memory(s->base,
+                    0x10000 << s->shift, IO_MEM_UNASSIGNED);
+}
+
+static void onenand_intr_update(struct onenand_s *s)
+{
+    qemu_set_irq(s->intr, ((s->intstatus >> 15) ^ (~s->config[0] >> 6)) & 1);
+}
+
+/* Hot reset (Reset OneNAND command) or warm reset (RP pin low) */
+static void onenand_reset(struct onenand_s *s, int cold)
+{
+    memset(&s->addr, 0, sizeof(s->addr));
+    s->command = 0;
+    s->count = 1;
+    s->bufaddr = 0;
+    s->config[0] = 0x40c0;
+    s->config[1] = 0x0000;
+    onenand_intr_update(s);
+    qemu_irq_raise(s->rdy);
+    s->status = 0x0000;
+    s->intstatus = cold ? 0x8080 : 0x8010;
+    s->unladdr[0] = 0;
+    s->unladdr[1] = 0;
+    s->wpstatus = 0x0002;
+    s->cycle = 0;
+    s->otpmode = 0;
+    s->bdrv_cur = s->bdrv;
+    s->current = s->image;
+    s->secs_cur = s->secs;
+
+    if (cold) {
+        /* Lock the whole flash */
+        memset(s->blockwp, ONEN_LOCK_LOCKED, s->blocks);
+
+        if (s->bdrv && bdrv_read(s->bdrv, 0, s->boot[0], 8) < 0)
+            cpu_abort(cpu_single_env, "%s: Loading the BootRAM failed.\n",
+                            __FUNCTION__);
+    }
+}
+
+static inline int onenand_load_main(struct onenand_s *s, int sec, int secn,
+                void *dest)
+{
+    if (s->bdrv_cur)
+        return bdrv_read(s->bdrv_cur, sec, dest, secn) < 0;
+    else if (sec + secn > s->secs_cur)
+        return 1;
+
+    memcpy(dest, s->current + (sec << 9), secn << 9);
+
+    return 0;
+}
+
+static inline int onenand_prog_main(struct onenand_s *s, int sec, int secn,
+                void *src)
+{
+    if (s->bdrv_cur)
+        return bdrv_write(s->bdrv_cur, sec, src, secn) < 0;
+    else if (sec + secn > s->secs_cur)
+        return 1;
+
+    memcpy(s->current + (sec << 9), src, secn << 9);
+
+    return 0;
+}
+
+static inline int onenand_load_spare(struct onenand_s *s, int sec, int secn,
+                void *dest)
+{
+    uint8_t buf[512];
+
+    if (s->bdrv_cur) {
+        if (bdrv_read(s->bdrv_cur, s->secs_cur + (sec >> 5), buf, 1) < 0)
+            return 1;
+        memcpy(dest, buf + ((sec & 31) << 4), secn << 4);
+    } else if (sec + secn > s->secs_cur)
+        return 1;
+    else
+        memcpy(dest, s->current + (s->secs_cur << 9) + (sec << 4), secn << 4);
+ 
+    return 0;
+}
+
+static inline int onenand_prog_spare(struct onenand_s *s, int sec, int secn,
+                void *src)
+{
+    uint8_t buf[512];
+
+    if (s->bdrv_cur) {
+        if (bdrv_read(s->bdrv_cur, s->secs_cur + (sec >> 5), buf, 1) < 0)
+            return 1;
+        memcpy(buf + ((sec & 31) << 4), src, secn << 4);
+        return bdrv_write(s->bdrv_cur, s->secs_cur + (sec >> 5), buf, 1) < 0;
+    } else if (sec + secn > s->secs_cur)
+        return 1;
+
+    memcpy(s->current + (s->secs_cur << 9) + (sec << 4), src, secn << 4);
+ 
+    return 0;
+}
+
+static inline int onenand_erase(struct onenand_s *s, int sec, int num)
+{
+    /* TODO: optimise */
+    uint8_t buf[512];
+
+    memset(buf, 0xff, sizeof(buf));
+    for (; num > 0; num --, sec ++) {
+        if (onenand_prog_main(s, sec, 1, buf))
+            return 1;
+        if (onenand_prog_spare(s, sec, 1, buf))
+            return 1;
+    }
+
+    return 0;
+}
+
+static void onenand_command(struct onenand_s *s, int cmd)
+{
+    int b;
+    int sec;
+    void *buf;
+#define SETADDR(block, page)			\
+    sec = (s->addr[page] & 3) +			\
+            ((((s->addr[page] >> 2) & 0x3f) +	\
+              (((s->addr[block] & 0xfff) |	\
+                (s->addr[block] >> 15 ?		\
+                 s->density_mask : 0)) << 6)) << (PAGE_SHIFT - 9));
+#define SETBUF_M()				\
+    buf = (s->bufaddr & 8) ?			\
+            s->data[(s->bufaddr >> 2) & 1][0] : s->boot[0];	\
+    buf += (s->bufaddr & 3) << 9;
+#define SETBUF_S()				\
+    buf = (s->bufaddr & 8) ?			\
+            s->data[(s->bufaddr >> 2) & 1][1] : s->boot[1];	\
+    buf += (s->bufaddr & 3) << 4;
+
+    switch (cmd) {
+    case 0x00:	/* Load single/multiple sector data unit into buffer */
+        SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE)
+
+        SETBUF_M()
+        if (onenand_load_main(s, sec, s->count, buf))
+            s->status |= ONEN_ERR_CMD | ONEN_ERR_LOAD;
+
+#if 0
+        SETBUF_S()
+        if (onenand_load_spare(s, sec, s->count, buf))
+            s->status |= ONEN_ERR_CMD | ONEN_ERR_LOAD;
+#endif
+
+        /* TODO: if (s->bufaddr & 3) + s->count was > 4 (2k-pages)
+         * or    if (s->bufaddr & 1) + s->count was > 2 (1k-pages)
+         * then we need two split the read/write into two chunks.
+         */
+        s->intstatus |= ONEN_INT | ONEN_INT_LOAD;
+        break;
+    case 0x13:	/* Load single/multiple spare sector into buffer */
+        SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE)
+
+        SETBUF_S()
+        if (onenand_load_spare(s, sec, s->count, buf))
+            s->status |= ONEN_ERR_CMD | ONEN_ERR_LOAD;
+
+        /* TODO: if (s->bufaddr & 3) + s->count was > 4 (2k-pages)
+         * or    if (s->bufaddr & 1) + s->count was > 2 (1k-pages)
+         * then we need two split the read/write into two chunks.
+         */
+        s->intstatus |= ONEN_INT | ONEN_INT_LOAD;
+        break;
+    case 0x80:	/* Program single/multiple sector data unit from buffer */
+        SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE)
+
+        SETBUF_M()
+        if (onenand_prog_main(s, sec, s->count, buf))
+            s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG;
+
+#if 0
+        SETBUF_S()
+        if (onenand_prog_spare(s, sec, s->count, buf))
+            s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG;
+#endif
+
+        /* TODO: if (s->bufaddr & 3) + s->count was > 4 (2k-pages)
+         * or    if (s->bufaddr & 1) + s->count was > 2 (1k-pages)
+         * then we need two split the read/write into two chunks.
+         */
+        s->intstatus |= ONEN_INT | ONEN_INT_PROG;
+        break;
+    case 0x1a:	/* Program single/multiple spare area sector from buffer */
+        SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE)
+
+        SETBUF_S()
+        if (onenand_prog_spare(s, sec, s->count, buf))
+            s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG;
+
+        /* TODO: if (s->bufaddr & 3) + s->count was > 4 (2k-pages)
+         * or    if (s->bufaddr & 1) + s->count was > 2 (1k-pages)
+         * then we need two split the read/write into two chunks.
+         */
+        s->intstatus |= ONEN_INT | ONEN_INT_PROG;
+        break;
+    case 0x1b:	/* Copy-back program */
+        SETBUF_S()
+
+        SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE)
+        if (onenand_load_main(s, sec, s->count, buf))
+            s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG;
+
+        SETADDR(ONEN_BUF_DEST_BLOCK, ONEN_BUF_DEST_PAGE)
+        if (onenand_prog_main(s, sec, s->count, buf))
+            s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG;
+
+        /* TODO: spare areas */
+
+        s->intstatus |= ONEN_INT | ONEN_INT_PROG;
+        break;
+
+    case 0x23:	/* Unlock NAND array block(s) */
+        s->intstatus |= ONEN_INT;
+
+        /* XXX the previous (?) area should be locked automatically */
+        for (b = s->unladdr[0]; b <= s->unladdr[1]; b ++) {
+            if (b >= s->blocks) {
+                s->status |= ONEN_ERR_CMD;
+                break;
+            }
+            if (s->blockwp[b] == ONEN_LOCK_LOCKTIGHTEN)
+                break;
+
+            s->wpstatus = s->blockwp[b] = ONEN_LOCK_UNLOCKED;
+        }
+        break;
+    case 0x2a:	/* Lock NAND array block(s) */
+        s->intstatus |= ONEN_INT;
+
+        for (b = s->unladdr[0]; b <= s->unladdr[1]; b ++) {
+            if (b >= s->blocks) {
+                s->status |= ONEN_ERR_CMD;
+                break;
+            }
+            if (s->blockwp[b] == ONEN_LOCK_LOCKTIGHTEN)
+                break;
+
+            s->wpstatus = s->blockwp[b] = ONEN_LOCK_LOCKED;
+        }
+        break;
+    case 0x2c:	/* Lock-tight NAND array block(s) */
+        s->intstatus |= ONEN_INT;
+
+        for (b = s->unladdr[0]; b <= s->unladdr[1]; b ++) {
+            if (b >= s->blocks) {
+                s->status |= ONEN_ERR_CMD;
+                break;
+            }
+            if (s->blockwp[b] == ONEN_LOCK_UNLOCKED)
+                continue;
+
+            s->wpstatus = s->blockwp[b] = ONEN_LOCK_LOCKTIGHTEN;
+        }
+        break;
+
+    case 0x71:	/* Erase-Verify-Read */
+        s->intstatus |= ONEN_INT;
+        break;
+    case 0x95:	/* Multi-block erase */
+        qemu_irq_pulse(s->intr);
+        /* Fall through.  */
+    case 0x94:	/* Block erase */
+        sec = ((s->addr[ONEN_BUF_BLOCK] & 0xfff) |
+                        (s->addr[ONEN_BUF_BLOCK] >> 15 ? s->density_mask : 0))
+                << (BLOCK_SHIFT - 9);
+        if (onenand_erase(s, sec, 1 << (BLOCK_SHIFT - 9)))
+            s->status |= ONEN_ERR_CMD | ONEN_ERR_ERASE;
+
+        s->intstatus |= ONEN_INT | ONEN_INT_ERASE;
+        break;
+    case 0xb0:	/* Erase suspend */
+        break;
+    case 0x30:	/* Erase resume */
+        s->intstatus |= ONEN_INT | ONEN_INT_ERASE;
+        break;
+
+    case 0xf0:	/* Reset NAND Flash core */
+        onenand_reset(s, 0);
+        break;
+    case 0xf3:	/* Reset OneNAND */
+        onenand_reset(s, 0);
+        break;
+
+    case 0x65:	/* OTP Access */
+        s->intstatus |= ONEN_INT;
+        s->bdrv_cur = 0;
+        s->current = s->otp;
+        s->secs_cur = 1 << (BLOCK_SHIFT - 9);
+        s->addr[ONEN_BUF_BLOCK] = 0;
+        s->otpmode = 1;
+        break;
+
+    default:
+        s->status |= ONEN_ERR_CMD;
+        s->intstatus |= ONEN_INT;
+        fprintf(stderr, "%s: unknown OneNAND command %x\n",
+                        __FUNCTION__, cmd);
+    }
+
+    onenand_intr_update(s);
+}
+
+static uint32_t onenand_read(void *opaque, target_phys_addr_t addr)
+{
+    struct onenand_s *s = (struct onenand_s *) opaque;
+    int offset = (addr - s->base) >> s->shift;
+
+    switch (offset) {
+    case 0x0000 ... 0xc000:
+        return lduw_le_p(s->boot[0] + (addr - s->base));
+
+    case 0xf000:	/* Manufacturer ID */
+        return (s->id >> 16) & 0xff;
+    case 0xf001:	/* Device ID */
+        return (s->id >>  8) & 0xff;
+    /* TODO: get the following values from a real chip!  */
+    case 0xf002:	/* Version ID */
+        return (s->id >>  0) & 0xff;
+    case 0xf003:	/* Data Buffer size */
+        return 1 << PAGE_SHIFT;
+    case 0xf004:	/* Boot Buffer size */
+        return 0x200;
+    case 0xf005:	/* Amount of buffers */
+        return 1 | (2 << 8);
+    case 0xf006:	/* Technology */
+        return 0;
+
+    case 0xf100 ... 0xf107:	/* Start addresses */
+        return s->addr[offset - 0xf100];
+
+    case 0xf200:	/* Start buffer */
+        return (s->bufaddr << 8) | ((s->count - 1) & (1 << (PAGE_SHIFT - 10)));
+
+    case 0xf220:	/* Command */
+        return s->command;
+    case 0xf221:	/* System Configuration 1 */
+        return s->config[0] & 0xffe0;
+    case 0xf222:	/* System Configuration 2 */
+        return s->config[1];
+
+    case 0xf240:	/* Controller Status */
+        return s->status;
+    case 0xf241:	/* Interrupt */
+        return s->intstatus;
+    case 0xf24c:	/* Unlock Start Block Address */
+        return s->unladdr[0];
+    case 0xf24d:	/* Unlock End Block Address */
+        return s->unladdr[1];
+    case 0xf24e:	/* Write Protection Status */
+        return s->wpstatus;
+
+    case 0xff00:	/* ECC Status */
+        return 0x00;
+    case 0xff01:	/* ECC Result of main area data */
+    case 0xff02:	/* ECC Result of spare area data */
+    case 0xff03:	/* ECC Result of main area data */
+    case 0xff04:	/* ECC Result of spare area data */
+        cpu_abort(cpu_single_env, "%s: imeplement ECC\n", __FUNCTION__);
+        return 0x0000;
+    }
+
+    fprintf(stderr, "%s: unknown OneNAND register %x\n",
+                    __FUNCTION__, offset);
+    return 0;
+}
+
+static void onenand_write(void *opaque, target_phys_addr_t addr,
+                uint32_t value)
+{
+    struct onenand_s *s = (struct onenand_s *) opaque;
+    int offset = (addr - s->base) >> s->shift;
+    int sec;
+
+    switch (offset) {
+    case 0x0000 ... 0x01ff:
+    case 0x8000 ... 0x800f:
+        if (s->cycle) {
+            s->cycle = 0;
+
+            if (value == 0x0000) {
+                SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE)
+                onenand_load_main(s, sec,
+                                1 << (PAGE_SHIFT - 9), s->data[0][0]);
+                s->addr[ONEN_BUF_PAGE] += 4;
+                s->addr[ONEN_BUF_PAGE] &= 0xff;
+            }
+            break;
+        }
+
+        switch (value) {
+        case 0x00f0:	/* Reset OneNAND */
+            onenand_reset(s, 0);
+            break;
+
+        case 0x00e0:	/* Load Data into Buffer */
+            s->cycle = 1;
+            break;
+
+        case 0x0090:	/* Read Identification Data */
+            memset(s->boot[0], 0, 3 << s->shift);
+            s->boot[0][0 << s->shift] = (s->id >> 16) & 0xff;
+            s->boot[0][1 << s->shift] = (s->id >>  8) & 0xff;
+            s->boot[0][2 << s->shift] = s->wpstatus & 0xff;
+            break;
+
+        default:
+            fprintf(stderr, "%s: unknown OneNAND boot command %x\n",
+                            __FUNCTION__, value);
+        }
+        break;
+
+    case 0xf100 ... 0xf107:	/* Start addresses */
+        s->addr[offset - 0xf100] = value;
+        break;
+
+    case 0xf200:	/* Start buffer */
+        s->bufaddr = (value >> 8) & 0xf;
+        if (PAGE_SHIFT == 11)
+            s->count = (value & 3) ?: 4;
+        else if (PAGE_SHIFT == 10)
+            s->count = (value & 1) ?: 2;
+        break;
+
+    case 0xf220:	/* Command */
+        if (s->intstatus & (1 << 15))
+            break;
+        s->command = value;
+        onenand_command(s, s->command);
+        break;
+    case 0xf221:	/* System Configuration 1 */
+        s->config[0] = value;
+        onenand_intr_update(s);
+        qemu_set_irq(s->rdy, (s->config[0] >> 7) & 1);
+        break;
+    case 0xf222:	/* System Configuration 2 */
+        s->config[1] = value;
+        break;
+
+    case 0xf241:	/* Interrupt */
+        s->intstatus &= value;
+        if ((1 << 15) & ~s->intstatus)
+            s->status &= ~(ONEN_ERR_CMD | ONEN_ERR_ERASE |
+                            ONEN_ERR_PROG | ONEN_ERR_LOAD);
+        onenand_intr_update(s);
+        break;
+    case 0xf24c:	/* Unlock Start Block Address */
+        s->unladdr[0] = value & (s->blocks - 1);
+        /* For some reason we have to set the end address to by default
+         * be same as start because the software forgets to write anything
+         * in there.  */
+        s->unladdr[1] = value & (s->blocks - 1);
+        break;
+    case 0xf24d:	/* Unlock End Block Address */
+        s->unladdr[1] = value & (s->blocks - 1);
+        break;
+
+    default:
+        fprintf(stderr, "%s: unknown OneNAND register %x\n",
+                        __FUNCTION__, offset);
+    }
+}
+
+static CPUReadMemoryFunc *onenand_readfn[] = {
+    onenand_read,	/* TODO */
+    onenand_read,
+    onenand_read,
+};
+
+static CPUWriteMemoryFunc *onenand_writefn[] = {
+    onenand_write,	/* TODO */
+    onenand_write,
+    onenand_write,
+};
+
+void *onenand_init(uint32_t id, int regshift, qemu_irq irq)
+{
+    struct onenand_s *s = (struct onenand_s *) qemu_mallocz(sizeof(*s));
+    int bdrv_index = drive_get_index(IF_MTD, 0, 0);
+    uint32_t size = 1 << (24 + ((id >> 12) & 7));
+    void *ram;
+
+    s->shift = regshift;
+    s->intr = irq;
+    s->rdy = 0;
+    s->id = id;
+    s->blocks = size >> BLOCK_SHIFT;
+    s->secs = size >> 9;
+    s->blockwp = qemu_malloc(s->blocks);
+    s->density_mask = (id & (1 << 11)) ? (1 << (6 + ((id >> 12) & 7))) : 0;
+    s->iomemtype = cpu_register_io_memory(0, onenand_readfn,
+                    onenand_writefn, s);
+    if (bdrv_index == -1)
+        s->image = memset(qemu_malloc(size + (size >> 5)),
+                        0xff, size + (size >> 5));
+    else
+        s->bdrv = drives_table[bdrv_index].bdrv;
+    s->otp = memset(qemu_malloc((64 + 2) << PAGE_SHIFT),
+                    0xff, (64 + 2) << PAGE_SHIFT);
+    s->ram = qemu_ram_alloc(0xc000 << s->shift);
+    ram = phys_ram_base + s->ram;
+    s->boot[0] = ram + (0x0000 << s->shift);
+    s->boot[1] = ram + (0x8000 << s->shift);
+    s->data[0][0] = ram + ((0x0200 + (0 << (PAGE_SHIFT - 1))) << s->shift);
+    s->data[0][1] = ram + ((0x8010 + (0 << (PAGE_SHIFT - 6))) << s->shift);
+    s->data[1][0] = ram + ((0x0200 + (1 << (PAGE_SHIFT - 1))) << s->shift);
+    s->data[1][1] = ram + ((0x8010 + (1 << (PAGE_SHIFT - 6))) << s->shift);
+
+    onenand_reset(s, 1);
+
+    return s;
+}
diff --git a/hw/palm.c b/hw/palm.c
index 9400ea7..8b767e2 100644
--- a/hw/palm.c
+++ b/hw/palm.c
@@ -25,6 +25,7 @@
 #include "omap.h"
 #include "boards.h"
 #include "arm-misc.h"
+#include "devices.h"
 
 static uint32_t static_readb(void *opaque, target_phys_addr_t offset)
 {
@@ -32,12 +33,14 @@ static uint32_t static_readb(void *opaque, target_phys_addr_t offset)
     return *val >> ((offset & 3) << 3);
 }
 
-static uint32_t static_readh(void *opaque, target_phys_addr_t offset) {
+static uint32_t static_readh(void *opaque, target_phys_addr_t offset)
+{
     uint32_t *val = (uint32_t *) opaque;
     return *val >> ((offset & 1) << 3);
 }
 
-static uint32_t static_readw(void *opaque, target_phys_addr_t offset) {
+static uint32_t static_readw(void *opaque, target_phys_addr_t offset)
+{
     uint32_t *val = (uint32_t *) opaque;
     return *val >> ((offset & 0) << 3);
 }
@@ -183,6 +186,12 @@ static void palmte_gpio_setup(struct omap_mpu_state_s *cpu)
     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(int ram_size, int vga_ram_size,
                 const char *boot_device, DisplayState *ds,
                 const char *kernel_filename, const char *kernel_cmdline,
@@ -190,7 +199,7 @@ static void palmte_init(int ram_size, int vga_ram_size,
 {
     struct omap_mpu_state_s *cpu;
     int flash_size = 0x00800000;
-    int sdram_size = 0x02000000;
+    int sdram_size = palmte_binfo.ram_size;
     int io;
     static uint32_t cs0val = 0xffffffff;
     static uint32_t cs1val = 0x0000e1a0;
@@ -250,10 +259,12 @@ static void palmte_init(int ram_size, int vga_ram_size,
     /* Load the kernel.  */
     if (kernel_filename) {
         /* Start at bootloader.  */
-        cpu->env->regs[15] = OMAP_EMIFF_BASE;
+        cpu->env->regs[15] = palmte_binfo.loader_start;
 
-        arm_load_kernel(cpu->env, sdram_size, kernel_filename, kernel_cmdline,
-                        initrd_filename, 0x331, OMAP_EMIFF_BASE);
+        palmte_binfo.kernel_filename = kernel_filename;
+        palmte_binfo.kernel_cmdline = kernel_cmdline;
+        palmte_binfo.initrd_filename = initrd_filename;
+        arm_load_kernel(cpu->env, &palmte_binfo);
     }
 
     dpy_resize(ds, 320, 320);
diff --git a/hw/realview.c b/hw/realview.c
index 29579d8..acf3b9e 100644
--- a/hw/realview.c
+++ b/hw/realview.c
@@ -18,6 +18,11 @@
 
 /* Board init.  */
 
+static struct arm_boot_info realview_binfo = {
+    .loader_start = 0x0,
+    .board_id = 0x33b,
+};
+
 static void realview_init(int ram_size, int vga_ram_size,
                      const char *boot_device, DisplayState *ds,
                      const char *kernel_filename, const char *kernel_cmdline,
@@ -177,8 +182,12 @@ static void realview_init(int ram_size, int vga_ram_size,
     /* 0x68000000 PCI mem 1.  */
     /* 0x6c000000 PCI mem 2.  */
 
-    arm_load_kernel(first_cpu, ram_size, kernel_filename, kernel_cmdline,
-                    initrd_filename, 0x33b, 0x0);
+    realview_binfo.ram_size = ram_size;
+    realview_binfo.kernel_filename = kernel_filename;
+    realview_binfo.kernel_cmdline = kernel_cmdline;
+    realview_binfo.initrd_filename = initrd_filename;
+    realview_binfo.nb_cpus = ncpu;
+    arm_load_kernel(first_cpu, &realview_binfo);
 
     /* ??? Hack to map an additional page of ram for the secondary CPU
        startup code.  I guess this works on real hardware because the
diff --git a/hw/sd.c b/hw/sd.c
index 1f71d85..de7dd89 100644
--- a/hw/sd.c
+++ b/hw/sd.c
@@ -37,7 +37,7 @@
 
 #ifdef DEBUG_SD
 #define DPRINTF(fmt, args...) \
-do { printf("SD: " fmt , ##args); } while (0)
+do { fprintf(stderr, "SD: " fmt , ##args); } while (0)
 #else
 #define DPRINTF(fmt, args...) do {} while(0)
 #endif
@@ -99,6 +99,8 @@ struct SDState {
     qemu_irq inserted_cb;
     BlockDriverState *bdrv;
     uint8_t *buf;
+
+    int enable;
 };
 
 static void sd_set_status(SDState *sd)
@@ -530,7 +532,7 @@ static void sd_lock_command(SDState *sd)
         sd->card_status &= ~CARD_IS_LOCKED;
         sd->pwd_len = 0;
         /* Erasing the entire card here! */
-        printf("SD: Card force-erased by CMD42\n");
+        fprintf(stderr, "SD: Card force-erased by CMD42\n");
         return;
     }
 
@@ -1076,7 +1078,7 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
         return sd_r1;
 
     case 56:	/* CMD56:  GEN_CMD */
-        printf("SD: GEN_CMD 0x%08x\n", req.arg);
+        fprintf(stderr, "SD: GEN_CMD 0x%08x\n", req.arg);
 
         switch (sd->state) {
         case sd_transfer_state:
@@ -1096,18 +1098,18 @@ static sd_rsp_type_t sd_normal_command(SDState *sd,
     bad_cmd:
         sd->card_status |= ILLEGAL_COMMAND;
 
-        printf("SD: Unknown CMD%i\n", req.cmd);
+        fprintf(stderr, "SD: Unknown CMD%i\n", req.cmd);
         return sd_r0;
 
     unimplemented_cmd:
         /* Commands that are recognised but not yet implemented in SPI mode.  */
         sd->card_status |= ILLEGAL_COMMAND;
-        printf ("SD: CMD%i not implemented in SPI mode\n", req.cmd);
+        fprintf(stderr, "SD: CMD%i not implemented in SPI mode\n", req.cmd);
         return sd_r0;
     }
 
     sd->card_status |= ILLEGAL_COMMAND;
-    printf("SD: CMD%i in a wrong state\n", req.cmd);
+    fprintf(stderr, "SD: CMD%i in a wrong state\n", req.cmd);
     return sd_r0;
 }
 
@@ -1217,7 +1219,7 @@ static sd_rsp_type_t sd_app_command(SDState *sd,
         return sd_normal_command(sd, req);
     }
 
-    printf("SD: ACMD%i in a wrong state\n", req.cmd);
+    fprintf(stderr, "SD: ACMD%i in a wrong state\n", req.cmd);
     return sd_r0;
 }
 
@@ -1227,7 +1229,7 @@ int sd_do_command(SDState *sd, struct sd_request_s *req,
     sd_rsp_type_t rtype;
     int rsplen;
 
-    if (!bdrv_is_inserted(sd->bdrv)) {
+    if (!bdrv_is_inserted(sd->bdrv) || !sd->enable) {
         return 0;
     }
 
@@ -1247,7 +1249,7 @@ int sd_do_command(SDState *sd, struct sd_request_s *req,
                           sd_cmd_class[req->cmd] == 7 ||
                           req->cmd == 16 || req->cmd == 55))) {
             sd->card_status |= ILLEGAL_COMMAND;
-            printf("SD: Card is locked\n");
+            fprintf(stderr, "SD: Card is locked\n");
             return 0;
         }
 
@@ -1321,7 +1323,7 @@ static void sd_blk_read(SDState *sd, uint32_t addr, uint32_t len)
     uint32_t end = addr + len;
 
     if (!sd->bdrv || bdrv_read(sd->bdrv, addr >> 9, sd->buf, 1) == -1) {
-        printf("sd_blk_read: read error on host side\n");
+        fprintf(stderr, "sd_blk_read: read error on host side\n");
         return;
     }
 
@@ -1329,7 +1331,7 @@ static void sd_blk_read(SDState *sd, uint32_t addr, uint32_t len)
         memcpy(sd->data, sd->buf + (addr & 511), 512 - (addr & 511));
 
         if (bdrv_read(sd->bdrv, end >> 9, sd->buf, 1) == -1) {
-            printf("sd_blk_read: read error on host side\n");
+            fprintf(stderr, "sd_blk_read: read error on host side\n");
             return;
         }
         memcpy(sd->data + 512 - (addr & 511), sd->buf, end & 511);
@@ -1343,28 +1345,28 @@ static void sd_blk_write(SDState *sd, uint32_t addr, uint32_t len)
 
     if ((addr & 511) || len < 512)
         if (!sd->bdrv || bdrv_read(sd->bdrv, addr >> 9, sd->buf, 1) == -1) {
-            printf("sd_blk_write: read error on host side\n");
+            fprintf(stderr, "sd_blk_write: read error on host side\n");
             return;
         }
 
     if (end > (addr & ~511) + 512) {
         memcpy(sd->buf + (addr & 511), sd->data, 512 - (addr & 511));
         if (bdrv_write(sd->bdrv, addr >> 9, sd->buf, 1) == -1) {
-            printf("sd_blk_write: write error on host side\n");
+            fprintf(stderr, "sd_blk_write: write error on host side\n");
             return;
         }
 
         if (bdrv_read(sd->bdrv, end >> 9, sd->buf, 1) == -1) {
-            printf("sd_blk_write: read error on host side\n");
+            fprintf(stderr, "sd_blk_write: read error on host side\n");
             return;
         }
         memcpy(sd->buf, sd->data + 512 - (addr & 511), end & 511);
         if (bdrv_write(sd->bdrv, end >> 9, sd->buf, 1) == -1)
-            printf("sd_blk_write: write error on host side\n");
+            fprintf(stderr, "sd_blk_write: write error on host side\n");
     } else {
         memcpy(sd->buf + (addr & 511), sd->data, len);
         if (!sd->bdrv || bdrv_write(sd->bdrv, addr >> 9, sd->buf, 1) == -1)
-            printf("sd_blk_write: write error on host side\n");
+            fprintf(stderr, "sd_blk_write: write error on host side\n");
     }
 }
 
@@ -1377,11 +1379,11 @@ void sd_write_data(SDState *sd, uint8_t value)
 {
     int i;
 
-    if (!sd->bdrv || !bdrv_is_inserted(sd->bdrv))
+    if (!sd->bdrv || !bdrv_is_inserted(sd->bdrv) || !sd->enable)
         return;
 
     if (sd->state != sd_receivingdata_state) {
-        printf("sd_write_data: not in Receiving-Data state\n");
+        fprintf(stderr, "sd_write_data: not in Receiving-Data state\n");
         return;
     }
 
@@ -1489,7 +1491,7 @@ void sd_write_data(SDState *sd, uint8_t value)
         break;
 
     default:
-        printf("sd_write_data: unknown command\n");
+        fprintf(stderr, "sd_write_data: unknown command\n");
         break;
     }
 }
@@ -1499,11 +1501,11 @@ uint8_t sd_read_data(SDState *sd)
     /* TODO: Append CRCs */
     uint8_t ret;
 
-    if (!sd->bdrv || !bdrv_is_inserted(sd->bdrv))
+    if (!sd->bdrv || !bdrv_is_inserted(sd->bdrv) || !sd->enable)
         return 0x00;
 
     if (sd->state != sd_sendingdata_state) {
-        printf("sd_read_data: not in Sending-Data state\n");
+        fprintf(stderr, "sd_read_data: not in Sending-Data state\n");
         return 0x00;
     }
 
@@ -1603,7 +1605,7 @@ uint8_t sd_read_data(SDState *sd)
         break;
 
     default:
-        printf("sd_read_data: unknown command\n");
+        fprintf(stderr, "sd_read_data: unknown command\n");
         return 0x00;
     }
 
@@ -1614,3 +1616,8 @@ int sd_data_ready(SDState *sd)
 {
     return sd->state == sd_sendingdata_state;
 }
+
+void sd_enable(SDState *sd, int enable)
+{
+    sd->enable = enable;
+}
diff --git a/hw/sd.h b/hw/sd.h
index 85f110f..cb7bc9c 100644
--- a/hw/sd.h
+++ b/hw/sd.h
@@ -74,6 +74,7 @@ void sd_write_data(SDState *sd, uint8_t value);
 uint8_t sd_read_data(SDState *sd);
 void sd_set_cb(SDState *sd, qemu_irq readonly, qemu_irq insert);
 int sd_data_ready(SDState *sd);
+void sd_enable(SDState *sd, int enable);
 
 /* ssi-sd.c */
 int ssi_sd_xfer(void *opaque, int val);
diff --git a/hw/spitz.c b/hw/spitz.c
index 159c633..b059f9a 100644
--- a/hw/spitz.c
+++ b/hw/spitz.c
@@ -1180,12 +1180,17 @@ static void sl_bootparam_write(uint32_t ptr)
 /* Board init.  */
 enum spitz_model_e { spitz, akita, borzoi, terrier };
 
+static struct arm_boot_info spitz_binfo = {
+    .loader_start = PXA2XX_SDRAM_BASE,
+    .ram_size = 0x04000000,
+};
+
 static void spitz_common_init(int ram_size, int vga_ram_size,
                 DisplayState *ds, const char *kernel_filename,
                 const char *kernel_cmdline, const char *initrd_filename,
                 const char *cpu_model, enum spitz_model_e model, int arm_id)
 {
-    uint32_t spitz_ram = 0x04000000;
+    uint32_t spitz_ram = spitz_binfo.ram_size;
     uint32_t spitz_rom = 0x00800000;
     struct pxa2xx_state_s *cpu;
     struct scoop_info_s *scp;
@@ -1230,10 +1235,13 @@ static void spitz_common_init(int ram_size, int vga_ram_size,
         spitz_microdrive_attach(cpu);
 
     /* Setup initial (reset) machine state */
-    cpu->env->regs[15] = PXA2XX_SDRAM_BASE;
+    cpu->env->regs[15] = spitz_binfo.loader_start;
 
-    arm_load_kernel(cpu->env, spitz_ram, kernel_filename, kernel_cmdline,
-                    initrd_filename, arm_id, PXA2XX_SDRAM_BASE);
+    spitz_binfo.kernel_filename = kernel_filename;
+    spitz_binfo.kernel_cmdline = kernel_cmdline;
+    spitz_binfo.initrd_filename = initrd_filename;
+    spitz_binfo.board_id = arm_id;
+    arm_load_kernel(cpu->env, &spitz_binfo);
     sl_bootparam_write(SL_PXA_PARAM_BASE - PXA2XX_SDRAM_BASE);
 }
 
diff --git a/hw/tmp105.c b/hw/tmp105.c
new file mode 100644
index 0000000..d9a3900
--- /dev/null
+++ b/hw/tmp105.c
@@ -0,0 +1,249 @@
+/*
+ * Texas Instruments TMP105 temperature sensor.
+ *
+ * Copyright (C) 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 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include "hw.h"
+#include "i2c.h"
+
+struct tmp105_s {
+    i2c_slave i2c;
+    int len;
+    uint8_t buf[2];
+    qemu_irq pin;
+
+    uint8_t pointer;
+    uint8_t config;
+    int16_t temperature;
+    int16_t limit[2];
+    int faults;
+    int alarm;
+};
+
+static void tmp105_interrupt_update(struct tmp105_s *s)
+{
+    qemu_set_irq(s->pin, s->alarm ^ ((~s->config >> 2) & 1));	/* POL */
+}
+
+static void tmp105_alarm_update(struct tmp105_s *s)
+{
+    if ((s->config >> 0) & 1) {					/* SD */
+        if ((s->config >> 7) & 1)				/* OS */
+            s->config &= ~(1 << 7);				/* OS */
+        else
+            return;
+    }
+
+    if ((s->config >> 1) & 1) {					/* TM */
+        if (s->temperature >= s->limit[1])
+            s->alarm = 1;
+        else if (s->temperature < s->limit[0])
+            s->alarm = 1;
+    } else {
+        if (s->temperature >= s->limit[1])
+            s->alarm = 1;
+        else if (s->temperature < s->limit[0])
+            s->alarm = 0;
+    }
+
+    tmp105_interrupt_update(s);
+}
+
+/* Units are 0.001 centigrades relative to 0 C.  */
+void tmp105_set(i2c_slave *i2c, int temp)
+{
+    struct tmp105_s *s = (struct tmp105_s *) i2c;
+
+    if (temp >= 128000 || temp < -128000) {
+        fprintf(stderr, "%s: values is out of range (%i.%03i C)\n",
+                        __FUNCTION__, temp / 1000, temp % 1000);
+        exit(-1);
+    }
+
+    s->temperature = ((int16_t) (temp * 0x800 / 128000)) << 4;
+
+    tmp105_alarm_update(s);
+}
+
+static const int tmp105_faultq[4] = { 1, 2, 4, 6 };
+
+static void tmp105_read(struct tmp105_s *s)
+{
+    s->len = 0;
+
+    if ((s->config >> 1) & 1) {					/* TM */
+        s->alarm = 0;
+        tmp105_interrupt_update(s);
+    }
+
+    switch (s->pointer & 3) {
+    case 0:	/* Temperature */
+        s->buf[s->len ++] = (((uint16_t) s->temperature) >> 8);
+        s->buf[s->len ++] = (((uint16_t) s->temperature) >> 0) &
+                (0xf0 << ((~s->config >> 5) & 3));		/* R */
+        break;
+
+    case 1:	/* Configuration */
+        s->buf[s->len ++] = s->config;
+        break;
+
+    case 2:	/* T_LOW */
+        s->buf[s->len ++] = ((uint16_t) s->limit[0]) >> 8;
+        s->buf[s->len ++] = ((uint16_t) s->limit[0]) >> 0;
+        break;
+
+    case 3:	/* T_HIGH */
+        s->buf[s->len ++] = ((uint16_t) s->limit[1]) >> 8;
+        s->buf[s->len ++] = ((uint16_t) s->limit[1]) >> 0;
+        break;
+    }
+}
+
+static void tmp105_write(struct tmp105_s *s)
+{
+    switch (s->pointer & 3) {
+    case 0:	/* Temperature */
+        break;
+
+    case 1:	/* Configuration */
+        if (s->buf[0] & ~s->config & (1 << 0))			/* SD */
+            printf("%s: TMP105 shutdown\n", __FUNCTION__);
+        s->config = s->buf[0];
+        s->faults = tmp105_faultq[(s->config >> 3) & 3];	/* F */
+        tmp105_alarm_update(s);
+        break;
+
+    case 2:	/* T_LOW */
+    case 3:	/* T_HIGH */
+        if (s->len >= 3)
+            s->limit[s->pointer & 1] = (int16_t)
+                    ((((uint16_t) s->buf[0]) << 8) | s->buf[1]);
+        tmp105_alarm_update(s);
+        break;
+    }
+}
+
+static int tmp105_rx(i2c_slave *i2c)
+{
+    struct tmp105_s *s = (struct tmp105_s *) i2c;
+
+    if (s->len < 2)
+        return s->buf[s->len ++];
+    else
+        return 0xff;
+}
+
+static int tmp105_tx(i2c_slave *i2c, uint8_t data)
+{
+    struct tmp105_s *s = (struct tmp105_s *) i2c;
+
+    if (!s->len ++)
+        s->pointer = data;
+    else {
+        if (s->len <= 2)
+            s->buf[s->len - 1] = data;
+        tmp105_write(s);
+    }
+
+    return 0;
+}
+
+static void tmp105_event(i2c_slave *i2c, enum i2c_event event)
+{
+    struct tmp105_s *s = (struct tmp105_s *) i2c;
+
+    if (event == I2C_START_RECV)
+        tmp105_read(s);
+
+    s->len = 0;
+}
+
+static void tmp105_save(QEMUFile *f, void *opaque)
+{
+    struct tmp105_s *s = (struct tmp105_s *) opaque;
+
+    qemu_put_byte(f, s->len);
+    qemu_put_8s(f, &s->buf[0]);
+    qemu_put_8s(f, &s->buf[1]);
+
+    qemu_put_8s(f, &s->pointer);
+    qemu_put_8s(f, &s->config);
+    qemu_put_be16s(f, &s->temperature);
+    qemu_put_be16s(f, &s->limit[0]);
+    qemu_put_be16s(f, &s->limit[1]);
+    qemu_put_byte(f, s->alarm);
+    s->faults = tmp105_faultq[(s->config >> 3) & 3];		/* F */
+
+    i2c_slave_save(f, &s->i2c);
+}
+
+static int tmp105_load(QEMUFile *f, void *opaque, int version_id)
+{
+    struct tmp105_s *s = (struct tmp105_s *) opaque;
+
+    s->len = qemu_get_byte(f);
+    qemu_get_8s(f, &s->buf[0]);
+    qemu_get_8s(f, &s->buf[1]);
+
+    qemu_get_8s(f, &s->pointer);
+    qemu_get_8s(f, &s->config);
+    qemu_get_be16s(f, &s->temperature);
+    qemu_get_be16s(f, &s->limit[0]);
+    qemu_get_be16s(f, &s->limit[1]);
+    s->alarm = qemu_get_byte(f);
+
+    tmp105_interrupt_update(s);
+
+    i2c_slave_load(f, &s->i2c);
+    return 0;
+}
+
+void tmp105_reset(i2c_slave *i2c)
+{
+    struct tmp105_s *s = (struct tmp105_s *) i2c;
+
+    s->temperature = 0;
+    s->pointer = 0;
+    s->config = 0;
+    s->faults = tmp105_faultq[(s->config >> 3) & 3];
+    s->alarm = 0;
+
+    tmp105_interrupt_update(s);
+}
+
+static int tmp105_iid = 0;
+
+struct i2c_slave *tmp105_init(i2c_bus *bus, qemu_irq alarm)
+{
+    struct tmp105_s *s = (struct tmp105_s *)
+            i2c_slave_init(bus, 0, sizeof(struct tmp105_s));
+
+    s->i2c.event = tmp105_event;
+    s->i2c.recv = tmp105_rx;
+    s->i2c.send = tmp105_tx;
+    s->pin = alarm;
+
+    tmp105_reset(&s->i2c);
+
+    register_savevm("TMP105", tmp105_iid ++, 0,
+                    tmp105_save, tmp105_load, s);
+
+    return &s->i2c;
+}
diff --git a/hw/tsc210x.c b/hw/tsc210x.c
index 96956a4..1654b8b 100644
--- a/hw/tsc210x.c
+++ b/hw/tsc210x.c
@@ -2,6 +2,7 @@
  * TI TSC2102 (touchscreen/sensors/audio controller) emulator.
  *
  * Copyright (c) 2006 Andrzej Zaborowski  <balrog@zabor.org>
+ * Copyright (C) 2008 Nokia Corporation
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -35,12 +36,15 @@
 
 struct tsc210x_state_s {
     qemu_irq pint;
+    qemu_irq kbint;
+    qemu_irq davint;
     QEMUTimer *timer;
     QEMUSoundCard card;
     struct uwire_slave_s chip;
     struct i2s_codec_s codec;
     uint8_t in_fifo[16384];
     uint8_t out_fifo[16384];
+    uint16_t model;
 
     int x, y;
     int pressure;
@@ -64,7 +68,7 @@ struct tsc210x_state_s {
     uint16_t audio_ctrl1;
     uint16_t audio_ctrl2;
     uint16_t audio_ctrl3;
-    uint16_t pll[2];
+    uint16_t pll[3];
     uint16_t volume;
     int64_t volume_change;
     int softstep;
@@ -78,6 +82,17 @@ struct tsc210x_state_s {
     int i2s_rx_rate;
     int i2s_tx_rate;
     AudioState *audio;
+
+    int tr[8];
+
+    struct {
+        uint16_t down;
+        uint16_t mask;
+        int scan;
+        int debounce;
+        int mode;
+        int intr;
+    } kb;
 };
 
 static const int resolution[4] = { 12, 8, 10, 12 };
@@ -118,17 +133,10 @@ static const uint16_t mode_regs[16] = {
     0x0000,	/* Y+, X- drivers */
 };
 
-/*
- * Convert screen coordinates to arbitrary values that the
- * touchscreen in my Palm Tungsten E device returns.
- * This shouldn't really matter (because the guest system
- * should calibrate the touchscreen anyway), but let's
- * imitate some real hardware.
- */
-#define X_TRANSFORM(value)		\
-    ((3850 - ((int) (value) * (3850 - 250) / 32768)) << 4)
-#define Y_TRANSFORM(value)		\
-    ((150 + ((int) (value) * (3037 - 150) / 32768)) << 4)
+#define X_TRANSFORM(s)			\
+    ((s->y * s->tr[0] - s->x * s->tr[1]) / s->tr[2] + s->tr[3])
+#define Y_TRANSFORM(s)			\
+    ((s->y * s->tr[4] - s->x * s->tr[5]) / s->tr[6] + s->tr[7])
 #define Z1_TRANSFORM(s)			\
     ((400 - ((s)->x >> 7) + ((s)->pressure << 10)) << 4)
 #define Z2_TRANSFORM(s)			\
@@ -161,6 +169,7 @@ static void tsc210x_reset(struct tsc210x_state_s *s)
     s->audio_ctrl3 = 0x0000;
     s->pll[0] = 0x1004;
     s->pll[1] = 0x0000;
+    s->pll[2] = 0x1fff;
     s->volume = 0xffff;
     s->dac_power = 0x8540;
     s->softstep = 1;
@@ -190,7 +199,15 @@ static void tsc210x_reset(struct tsc210x_state_s *s)
     s->i2s_tx_rate = 0;
     s->i2s_rx_rate = 0;
 
+    s->kb.scan = 1;
+    s->kb.debounce = 0;
+    s->kb.mask = 0x0000;
+    s->kb.mode = 3;
+    s->kb.intr = 0;
+
     qemu_set_irq(s->pint, !s->irq);
+    qemu_set_irq(s->davint, !s->dav);
+    qemu_irq_raise(s->kbint);
 }
 
 struct tsc210x_rate_info_s {
@@ -344,13 +361,13 @@ static uint16_t tsc2102_data_register_read(struct tsc210x_state_s *s, int reg)
     switch (reg) {
     case 0x00:	/* X */
         s->dav &= 0xfbff;
-        return TSC_CUT_RESOLUTION(X_TRANSFORM(s->x), s->precision) +
+        return TSC_CUT_RESOLUTION(X_TRANSFORM(s), s->precision) +
                 (s->noise & 3);
 
     case 0x01:	/* Y */
         s->noise ++;
         s->dav &= 0xfdff;
-        return TSC_CUT_RESOLUTION(Y_TRANSFORM(s->y), s->precision) ^
+        return TSC_CUT_RESOLUTION(Y_TRANSFORM(s), s->precision) ^
                 (s->noise & 3);
 
     case 0x02:	/* Z1 */
@@ -364,6 +381,14 @@ static uint16_t tsc2102_data_register_read(struct tsc210x_state_s *s, int reg)
                 (s->noise & 3);
 
     case 0x04:	/* KPData */
+        if ((s->model & 0xff00) == 0x2300) {
+            if (s->kb.intr && (s->kb.mode & 2)) {
+                s->kb.intr = 0;
+                qemu_irq_raise(s->kbint);
+            }
+            return s->kb.down;
+        }
+
         return 0xffff;
 
     case 0x05:	/* BAT1 */
@@ -414,9 +439,19 @@ static uint16_t tsc2102_control_register_read(
         return (s->pressure << 15) | ((!s->busy) << 14) |
                 (s->nextfunction << 10) | (s->nextprecision << 8) | s->filter; 
 
-    case 0x01:	/* Status */
-        return (s->pin_func << 14) | ((!s->enabled) << 13) |
-                (s->host_mode << 12) | ((!!s->dav) << 11) | s->dav;
+    case 0x01:	/* Status / Keypad Control */
+        if ((s->model & 0xff00) == 0x2100)
+            return (s->pin_func << 14) | ((!s->enabled) << 13) |
+                    (s->host_mode << 12) | ((!!s->dav) << 11) | s->dav;
+        else
+            return (s->kb.intr << 15) | ((s->kb.scan || !s->kb.down) << 14) |
+                    (s->kb.debounce << 11);
+
+    case 0x02:	/* DAC Control */
+        if ((s->model & 0xff00) == 0x2300)
+            return s->dac_power & 0x8000;
+        else
+            goto bad_reg;
 
     case 0x03:	/* Reference */
         return s->ref;
@@ -427,7 +462,18 @@ static uint16_t tsc2102_control_register_read(
     case 0x05:	/* Configuration */
         return s->timing;
 
+    case 0x06:	/* Secondary configuration */
+        if ((s->model & 0xff00) == 0x2100)
+            goto bad_reg;
+        return ((!s->dav) << 15) | ((s->kb.mode & 1) << 14) | s->pll[2];
+
+    case 0x10:	/* Keypad Mask */
+        if ((s->model & 0xff00) == 0x2100)
+            goto bad_reg;
+        return s->kb.mask;
+
     default:
+    bad_reg:
 #ifdef TSC_VERBOSE
         fprintf(stderr, "tsc2102_control_register_read: "
                         "no such register: 0x%02x\n", reg);
@@ -556,10 +602,27 @@ static void tsc2102_control_register_write(
         s->filter = value & 0xff;
         return;
 
-    case 0x01:	/* Status */
-        s->pin_func = value >> 14;
+    case 0x01:	/* Status / Keypad Control */
+        if ((s->model & 0xff00) == 0x2100)
+            s->pin_func = value >> 14;
+	else {
+            s->kb.scan = (value >> 14) & 1;
+            s->kb.debounce = (value >> 11) & 7;
+            if (s->kb.intr && s->kb.scan) {
+                s->kb.intr = 0;
+                qemu_irq_raise(s->kbint);
+            }
+        }
         return;
 
+    case 0x02:	/* DAC Control */
+        if ((s->model & 0xff00) == 0x2300) {
+            s->dac_power &= 0x7fff;
+            s->dac_power |= 0x8000 & value;
+        } else
+            goto bad_reg;
+        break;
+
     case 0x03:	/* Reference */
         s->ref = value & 0x1f;
         return;
@@ -586,7 +649,21 @@ static void tsc2102_control_register_write(
 #endif
         return;
 
+    case 0x06:	/* Secondary configuration */
+        if ((s->model & 0xff00) == 0x2100)
+            goto bad_reg;
+        s->kb.mode = value >> 14;
+        s->pll[2] = value & 0x3ffff;
+        return;
+
+    case 0x10:	/* Keypad Mask */
+        if ((s->model & 0xff00) == 0x2100)
+            goto bad_reg;
+        s->kb.mask = value;
+        return;
+
     default:
+    bad_reg:
 #ifdef TSC_VERBOSE
         fprintf(stderr, "tsc2102_control_register_write: "
                         "no such register: 0x%02x\n", reg);
@@ -785,7 +862,7 @@ static void tsc210x_pin_update(struct tsc210x_state_s *s)
         return;
     }
 
-    if (!s->enabled || s->busy)
+    if (!s->enabled || s->busy || s->dav)
         return;
 
     s->busy = 1;
@@ -805,6 +882,8 @@ static uint16_t tsc210x_read(struct tsc210x_state_s *s)
     switch (s->page) {
     case TSC_DATA_REGISTERS_PAGE:
         ret = tsc2102_data_register_read(s, s->offset);
+        if (!s->dav)
+            qemu_irq_raise(s->davint);
         break;
     case TSC_CONTROL_REGISTERS_PAGE:
         ret = tsc2102_control_register_read(s, s->offset);
@@ -859,6 +938,22 @@ static void tsc210x_write(struct tsc210x_state_s *s, uint16_t value)
     }
 }
 
+uint32_t tsc210x_txrx(void *opaque, uint32_t value)
+{
+    struct tsc210x_state_s *s = opaque;
+    uint32_t ret = 0;
+
+    /* TODO: sequential reads etc - how do we make sure the host doesn't
+     * unintentionally read out a conversion result from a register while
+     * transmitting the command word of the next command?  */
+    if (!value || (s->state && s->command))
+        ret = tsc210x_read(s);
+    if (value || (s->state && !s->command))
+        tsc210x_write(s, value);
+
+    return ret;
+}
+
 static void tsc210x_timer_tick(void *opaque)
 {
     struct tsc210x_state_s *s = opaque;
@@ -871,6 +966,7 @@ static void tsc210x_timer_tick(void *opaque)
     s->busy = 0;
     s->dav |= mode_regs[s->function];
     tsc210x_pin_update(s);
+    qemu_irq_lower(s->davint);
 }
 
 static void tsc210x_touchscreen_event(void *opaque,
@@ -1001,6 +1097,7 @@ static int tsc210x_load(QEMUFile *f, void *opaque, int version_id)
 
     s->busy = qemu_timer_pending(s->timer);
     qemu_set_irq(s->pint, !s->irq);
+    qemu_set_irq(s->davint, !s->dav);
 
     return 0;
 }
@@ -1020,9 +1117,19 @@ struct uwire_slave_s *tsc2102_init(qemu_irq pint, AudioState *audio)
     s->precision = s->nextprecision = 0;
     s->timer = qemu_new_timer(vm_clock, tsc210x_timer_tick, s);
     s->pint = pint;
+    s->model = 0x2102;
     s->name = "tsc2102";
     s->audio = audio;
 
+    s->tr[0] = 0;
+    s->tr[1] = 1;
+    s->tr[2] = 0;
+    s->tr[3] = 1;
+    s->tr[4] = 1;
+    s->tr[5] = 0;
+    s->tr[6] = 0;
+    s->tr[7] = 1;
+
     s->chip.opaque = s;
     s->chip.send = (void *) tsc210x_write;
     s->chip.receive = (void *) tsc210x_read;
@@ -1048,9 +1155,147 @@ struct uwire_slave_s *tsc2102_init(qemu_irq pint, AudioState *audio)
     return &s->chip;
 }
 
+struct uwire_slave_s *tsc2301_init(qemu_irq penirq, qemu_irq kbirq,
+                qemu_irq dav, AudioState *audio)
+{
+    struct tsc210x_state_s *s;
+
+    s = (struct tsc210x_state_s *)
+            qemu_mallocz(sizeof(struct tsc210x_state_s));
+    memset(s, 0, sizeof(struct tsc210x_state_s));
+    s->x = 400;
+    s->y = 240;
+    s->pressure = 0;
+    s->precision = s->nextprecision = 0;
+    s->timer = qemu_new_timer(vm_clock, tsc210x_timer_tick, s);
+    s->pint = penirq;
+    s->kbint = kbirq;
+    s->davint = dav;
+    s->model = 0x2301;
+    s->name = "tsc2301";
+    s->audio = audio;
+
+    s->tr[0] = 0;
+    s->tr[1] = 1;
+    s->tr[2] = 0;
+    s->tr[3] = 1;
+    s->tr[4] = 1;
+    s->tr[5] = 0;
+    s->tr[6] = 0;
+    s->tr[7] = 1;
+
+    s->chip.opaque = s;
+    s->chip.send = (void *) tsc210x_write;
+    s->chip.receive = (void *) tsc210x_read;
+
+    s->codec.opaque = s;
+    s->codec.tx_swallow = (void *) tsc210x_i2s_swallow;
+    s->codec.set_rate = (void *) tsc210x_i2s_set_rate;
+    s->codec.in.fifo = s->in_fifo;
+    s->codec.out.fifo = s->out_fifo;
+
+    tsc210x_reset(s);
+
+    qemu_add_mouse_event_handler(tsc210x_touchscreen_event, s, 1,
+                    "QEMU TSC2301-driven Touchscreen");
+
+    if (s->audio)
+        AUD_register_card(s->audio, s->name, &s->card);
+
+    qemu_register_reset((void *) tsc210x_reset, s);
+    register_savevm(s->name, tsc2102_iid ++, 0,
+                    tsc210x_save, tsc210x_load, s);
+
+    return &s->chip;
+}
+
 struct i2s_codec_s *tsc210x_codec(struct uwire_slave_s *chip)
 {
     struct tsc210x_state_s *s = (struct tsc210x_state_s *) chip->opaque;
 
     return &s->codec;
 }
+
+/*
+ * Use tslib generated calibration data to generate ADC input values
+ * from the touchscreen.  Assuming 12-bit precision was used during
+ * tslib calibration.
+ */
+void tsc210x_set_transform(struct uwire_slave_s *chip,
+                struct mouse_transform_info_s *info)
+{
+    struct tsc210x_state_s *s = (struct tsc210x_state_s *) chip->opaque;
+#if 0
+    int64_t ltr[8];
+
+    ltr[0] = (int64_t) info->a[1] * info->y;
+    ltr[1] = (int64_t) info->a[4] * info->x;
+    ltr[2] = (int64_t) info->a[1] * info->a[3] -
+            (int64_t) info->a[4] * info->a[0];
+    ltr[3] = (int64_t) info->a[2] * info->a[4] -
+            (int64_t) info->a[5] * info->a[1];
+    ltr[4] = (int64_t) info->a[0] * info->y;
+    ltr[5] = (int64_t) info->a[3] * info->x;
+    ltr[6] = (int64_t) info->a[4] * info->a[0] -
+            (int64_t) info->a[1] * info->a[3];
+    ltr[7] = (int64_t) info->a[2] * info->a[3] -
+            (int64_t) info->a[5] * info->a[0];
+
+    /* Avoid integer overflow */
+    s->tr[0] = ltr[0] >> 11;
+    s->tr[1] = ltr[1] >> 11;
+    s->tr[2] = muldiv64(ltr[2], 1, info->a[6]);
+    s->tr[3] = muldiv64(ltr[3], 1 << 4, ltr[2]);
+    s->tr[4] = ltr[4] >> 11;
+    s->tr[5] = ltr[5] >> 11;
+    s->tr[6] = muldiv64(ltr[6], 1, info->a[6]);
+    s->tr[7] = muldiv64(ltr[7], 1 << 4, ltr[6]);
+#else
+
+    if (abs(info->a[0]) > abs(info->a[1])) {
+        s->tr[0] = 0;
+        s->tr[1] = -info->a[6] * info->x;
+        s->tr[2] = info->a[0];
+        s->tr[3] = -info->a[2] / info->a[0];
+        s->tr[4] = info->a[6] * info->y;
+        s->tr[5] = 0;
+        s->tr[6] = info->a[4];
+        s->tr[7] = -info->a[5] / info->a[4];
+    } else {
+        s->tr[0] = info->a[6] * info->y;
+        s->tr[1] = 0;
+        s->tr[2] = info->a[1];
+        s->tr[3] = -info->a[2] / info->a[1];
+        s->tr[4] = 0;
+        s->tr[5] = -info->a[6] * info->x;
+        s->tr[6] = info->a[3];
+        s->tr[7] = -info->a[5] / info->a[3];
+    }
+
+    s->tr[0] >>= 11;
+    s->tr[1] >>= 11;
+    s->tr[3] <<= 4;
+    s->tr[4] >>= 11;
+    s->tr[5] >>= 11;
+    s->tr[7] <<= 4;
+#endif
+}
+
+void tsc210x_key_event(struct uwire_slave_s *chip, int key, int down)
+{
+    struct tsc210x_state_s *s = (struct tsc210x_state_s *) chip->opaque;
+
+    if (down)
+        s->kb.down |= 1 << key;
+    else
+        s->kb.down &= ~(1 << key);
+
+    if (down && (s->kb.down & ~s->kb.mask) && !s->kb.intr) {
+        s->kb.intr = 1;
+        qemu_irq_lower(s->kbint);
+    } else if (s->kb.intr && !(s->kb.down & ~s->kb.mask) &&
+                    !(s->kb.mode & 1)) {
+        s->kb.intr = 0;
+        qemu_irq_raise(s->kbint);
+    }
+}
diff --git a/hw/twl92230.c b/hw/twl92230.c
new file mode 100644
index 0000000..11a5d1a
--- /dev/null
+++ b/hw/twl92230.c
@@ -0,0 +1,923 @@
+/*
+ * TI TWL92230C energy-management companion device for the OMAP24xx.
+ * Aka. Menelaus (N4200 MENELAUS1_V2.2)
+ *
+ * Copyright (C) 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 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#include "hw.h"
+#include "qemu-timer.h"
+#include "i2c.h"
+#include "sysemu.h"
+#include "console.h"
+
+#define VERBOSE 1
+
+struct menelaus_s {
+    i2c_slave i2c;
+    qemu_irq irq;
+
+    int firstbyte;
+    uint8_t reg;
+
+    uint8_t vcore[5];
+    uint8_t dcdc[3];
+    uint8_t ldo[8];
+    uint8_t sleep[2];
+    uint8_t osc;
+    uint8_t detect;
+    uint16_t mask;
+    uint16_t status;
+    uint8_t dir;
+    uint8_t inputs;
+    uint8_t outputs;
+    uint8_t bbsms;
+    uint8_t pull[4];
+    uint8_t mmc_ctrl[3];
+    uint8_t mmc_debounce;
+    struct {
+        uint8_t ctrl;
+        uint16_t comp;
+        QEMUTimer *hz;
+        int64_t next;
+        struct tm tm;
+        struct tm new;
+        struct tm alm;
+        time_t sec;
+        time_t alm_sec;
+        time_t next_comp;
+        struct tm *(*gettime)(const time_t *timep, struct tm *result);
+    } rtc;
+    qemu_irq handler[3];
+    qemu_irq *in;
+    int pwrbtn_state;
+    qemu_irq pwrbtn;
+};
+
+static inline void menelaus_update(struct menelaus_s *s)
+{
+    qemu_set_irq(s->irq, s->status & ~s->mask);
+}
+
+static inline void menelaus_rtc_start(struct menelaus_s *s)
+{
+    s->rtc.next =+ qemu_get_clock(rt_clock);
+    qemu_mod_timer(s->rtc.hz, s->rtc.next);
+}
+
+static inline void menelaus_rtc_stop(struct menelaus_s *s)
+{
+    qemu_del_timer(s->rtc.hz);
+    s->rtc.next =- qemu_get_clock(rt_clock);
+    if (s->rtc.next < 1)
+        s->rtc.next = 1;
+}
+
+static void menelaus_rtc_update(struct menelaus_s *s)
+{
+    s->rtc.gettime(&s->rtc.sec, &s->rtc.tm);
+}
+
+static void menelaus_alm_update(struct menelaus_s *s)
+{
+    if ((s->rtc.ctrl & 3) == 3)
+        s->rtc.alm_sec = mktime(&s->rtc.alm);
+}
+
+static void menelaus_rtc_hz(void *opaque)
+{
+    struct menelaus_s *s = (struct menelaus_s *) opaque;
+
+    s->rtc.sec ++;
+    s->rtc.next += 1000;
+    qemu_mod_timer(s->rtc.hz, s->rtc.next);
+    if ((s->rtc.ctrl >> 3) & 3) {				/* EVERY */
+        menelaus_rtc_update(s);
+        if (((s->rtc.ctrl >> 3) & 3) == 1 && !s->rtc.tm.tm_sec)
+            s->status |= 1 << 8;				/* RTCTMR */
+        else if (((s->rtc.ctrl >> 3) & 3) == 2 && !s->rtc.tm.tm_min)
+            s->status |= 1 << 8;				/* RTCTMR */
+        else if (!s->rtc.tm.tm_hour)
+            s->status |= 1 << 8;				/* RTCTMR */
+    } else
+        s->status |= 1 << 8;					/* RTCTMR */
+    if ((s->rtc.ctrl >> 1) & 1) {				/* RTC_AL_EN */
+        if (s->rtc.sec == s->rtc.alm_sec)
+            s->status |= 1 << 9;				/* RTCALM */
+        /* TODO: wake-up */
+    }
+    if (s->rtc.next_comp >= s->rtc.sec) {
+        s->rtc.next -= muldiv64((int16_t) s->rtc.comp, 1000, 0x8000);
+        s->rtc.next_comp = s->rtc.sec + 3600;
+    }
+    menelaus_update(s);
+}
+
+void menelaus_reset(i2c_slave *i2c)
+{
+    struct menelaus_s *s = (struct menelaus_s *) i2c;
+    time_t ti;
+    s->reg = 0x00;
+
+    s->vcore[0] = 0x0c;	/* XXX: X-loader needs 0x8c? check!  */
+    s->vcore[1] = 0x05;
+    s->vcore[2] = 0x02;
+    s->vcore[3] = 0x0c;
+    s->vcore[4] = 0x03;
+    s->dcdc[0] = 0x33;	/* Depends on wiring */
+    s->dcdc[1] = 0x03;
+    s->dcdc[2] = 0x00;
+    s->ldo[0] = 0x95;
+    s->ldo[1] = 0x7e;
+    s->ldo[2] = 0x00;
+    s->ldo[3] = 0x00;	/* Depends on wiring */
+    s->ldo[4] = 0x03;	/* Depends on wiring */
+    s->ldo[5] = 0x00;
+    s->ldo[6] = 0x00;
+    s->ldo[7] = 0x00;
+    s->sleep[0] = 0x00;
+    s->sleep[1] = 0x00;
+    s->osc = 0x01;
+    s->detect = 0x09;
+    s->mask = 0x0fff;
+    s->status = 0;
+    s->dir = 0x07;
+    s->outputs = 0x00;
+    s->bbsms = 0x00;
+    s->pull[0] = 0x00;
+    s->pull[1] = 0x00;
+    s->pull[2] = 0x00;
+    s->pull[3] = 0x00;
+    s->mmc_ctrl[0] = 0x03;
+    s->mmc_ctrl[1] = 0xc0;
+    s->mmc_ctrl[2] = 0x00;
+    s->mmc_debounce = 0x05;
+
+    time(&ti);
+    if (s->rtc.ctrl & 1)
+        menelaus_rtc_stop(s);
+    s->rtc.ctrl = 0x00;
+    s->rtc.comp = 0x0000;
+    s->rtc.next = 1000;
+    s->rtc.sec = ti;
+    s->rtc.next_comp = s->rtc.sec + 1800;
+    s->rtc.alm.tm_sec = 0x00;
+    s->rtc.alm.tm_min = 0x00;
+    s->rtc.alm.tm_hour = 0x00;
+    s->rtc.alm.tm_mday = 0x01;
+    s->rtc.alm.tm_mon = 0x00;
+    s->rtc.alm.tm_year = 2004;
+    menelaus_update(s);
+}
+
+static inline uint8_t to_bcd(int val)
+{
+    return ((val / 10) << 4) | (val % 10);
+}
+
+static inline int from_bcd(uint8_t val)
+{
+    return ((val >> 4) * 10) + (val & 0x0f);
+}
+
+static void menelaus_gpio_set(void *opaque, int line, int level)
+{
+    struct menelaus_s *s = (struct menelaus_s *) opaque;
+
+    /* No interrupt generated */
+    s->inputs &= ~(1 << line);
+    s->inputs |= level << line;
+}
+
+static void menelaus_pwrbtn_set(void *opaque, int line, int level)
+{
+    struct menelaus_s *s = (struct menelaus_s *) opaque;
+
+    if (!s->pwrbtn_state && level) {
+        s->status |= 1 << 11;					/* PSHBTN */
+        menelaus_update(s);
+    }
+    s->pwrbtn_state = level;
+}
+
+#define MENELAUS_REV		0x01
+#define MENELAUS_VCORE_CTRL1	0x02
+#define MENELAUS_VCORE_CTRL2	0x03
+#define MENELAUS_VCORE_CTRL3	0x04
+#define MENELAUS_VCORE_CTRL4	0x05
+#define MENELAUS_VCORE_CTRL5	0x06
+#define MENELAUS_DCDC_CTRL1	0x07
+#define MENELAUS_DCDC_CTRL2	0x08
+#define MENELAUS_DCDC_CTRL3	0x09
+#define MENELAUS_LDO_CTRL1	0x0a
+#define MENELAUS_LDO_CTRL2	0x0b
+#define MENELAUS_LDO_CTRL3	0x0c
+#define MENELAUS_LDO_CTRL4	0x0d
+#define MENELAUS_LDO_CTRL5	0x0e
+#define MENELAUS_LDO_CTRL6	0x0f
+#define MENELAUS_LDO_CTRL7	0x10
+#define MENELAUS_LDO_CTRL8	0x11
+#define MENELAUS_SLEEP_CTRL1	0x12
+#define MENELAUS_SLEEP_CTRL2	0x13
+#define MENELAUS_DEVICE_OFF	0x14
+#define MENELAUS_OSC_CTRL	0x15
+#define MENELAUS_DETECT_CTRL	0x16
+#define MENELAUS_INT_MASK1	0x17
+#define MENELAUS_INT_MASK2	0x18
+#define MENELAUS_INT_STATUS1	0x19
+#define MENELAUS_INT_STATUS2	0x1a
+#define MENELAUS_INT_ACK1	0x1b
+#define MENELAUS_INT_ACK2	0x1c
+#define MENELAUS_GPIO_CTRL	0x1d
+#define MENELAUS_GPIO_IN	0x1e
+#define MENELAUS_GPIO_OUT	0x1f
+#define MENELAUS_BBSMS		0x20
+#define MENELAUS_RTC_CTRL	0x21
+#define MENELAUS_RTC_UPDATE	0x22
+#define MENELAUS_RTC_SEC	0x23
+#define MENELAUS_RTC_MIN	0x24
+#define MENELAUS_RTC_HR		0x25
+#define MENELAUS_RTC_DAY	0x26
+#define MENELAUS_RTC_MON	0x27
+#define MENELAUS_RTC_YR		0x28
+#define MENELAUS_RTC_WKDAY	0x29
+#define MENELAUS_RTC_AL_SEC	0x2a
+#define MENELAUS_RTC_AL_MIN	0x2b
+#define MENELAUS_RTC_AL_HR	0x2c
+#define MENELAUS_RTC_AL_DAY	0x2d
+#define MENELAUS_RTC_AL_MON	0x2e
+#define MENELAUS_RTC_AL_YR	0x2f
+#define MENELAUS_RTC_COMP_MSB	0x30
+#define MENELAUS_RTC_COMP_LSB	0x31
+#define MENELAUS_S1_PULL_EN	0x32
+#define MENELAUS_S1_PULL_DIR	0x33
+#define MENELAUS_S2_PULL_EN	0x34
+#define MENELAUS_S2_PULL_DIR	0x35
+#define MENELAUS_MCT_CTRL1	0x36
+#define MENELAUS_MCT_CTRL2	0x37
+#define MENELAUS_MCT_CTRL3	0x38
+#define MENELAUS_MCT_PIN_ST	0x39
+#define MENELAUS_DEBOUNCE1	0x3a
+
+static uint8_t menelaus_read(void *opaque, uint8_t addr)
+{
+    struct menelaus_s *s = (struct menelaus_s *) opaque;
+    int reg = 0;
+
+    switch (addr) {
+    case MENELAUS_REV:
+        return 0x22;
+
+    case MENELAUS_VCORE_CTRL5: reg ++;
+    case MENELAUS_VCORE_CTRL4: reg ++;
+    case MENELAUS_VCORE_CTRL3: reg ++;
+    case MENELAUS_VCORE_CTRL2: reg ++;
+    case MENELAUS_VCORE_CTRL1:
+        return s->vcore[reg];
+
+    case MENELAUS_DCDC_CTRL3: reg ++;
+    case MENELAUS_DCDC_CTRL2: reg ++;
+    case MENELAUS_DCDC_CTRL1:
+        return s->dcdc[reg];
+
+    case MENELAUS_LDO_CTRL8: reg ++;
+    case MENELAUS_LDO_CTRL7: reg ++;
+    case MENELAUS_LDO_CTRL6: reg ++;
+    case MENELAUS_LDO_CTRL5: reg ++;
+    case MENELAUS_LDO_CTRL4: reg ++;
+    case MENELAUS_LDO_CTRL3: reg ++;
+    case MENELAUS_LDO_CTRL2: reg ++;
+    case MENELAUS_LDO_CTRL1:
+        return s->ldo[reg];
+
+    case MENELAUS_SLEEP_CTRL2: reg ++;
+    case MENELAUS_SLEEP_CTRL1:
+        return s->sleep[reg];
+
+    case MENELAUS_DEVICE_OFF:
+        return 0;
+
+    case MENELAUS_OSC_CTRL:
+        return s->osc | (1 << 7);			/* CLK32K_GOOD */
+
+    case MENELAUS_DETECT_CTRL:
+        return s->detect;
+
+    case MENELAUS_INT_MASK1:
+        return (s->mask >> 0) & 0xff;
+    case MENELAUS_INT_MASK2:
+        return (s->mask >> 8) & 0xff;
+
+    case MENELAUS_INT_STATUS1:
+        return (s->status >> 0) & 0xff;
+    case MENELAUS_INT_STATUS2:
+        return (s->status >> 8) & 0xff;
+
+    case MENELAUS_INT_ACK1:
+    case MENELAUS_INT_ACK2:
+        return 0;
+
+    case MENELAUS_GPIO_CTRL:
+        return s->dir;
+    case MENELAUS_GPIO_IN:
+        return s->inputs | (~s->dir & s->outputs);
+    case MENELAUS_GPIO_OUT:
+        return s->outputs;
+
+    case MENELAUS_BBSMS:
+        return s->bbsms;
+
+    case MENELAUS_RTC_CTRL:
+        return s->rtc.ctrl;
+    case MENELAUS_RTC_UPDATE:
+        return 0x00;
+    case MENELAUS_RTC_SEC:
+        menelaus_rtc_update(s);
+        return to_bcd(s->rtc.tm.tm_sec);
+    case MENELAUS_RTC_MIN:
+        menelaus_rtc_update(s);
+        return to_bcd(s->rtc.tm.tm_min);
+    case MENELAUS_RTC_HR:
+        menelaus_rtc_update(s);
+        if ((s->rtc.ctrl >> 2) & 1)			/* MODE12_n24 */
+            return to_bcd((s->rtc.tm.tm_hour % 12) + 1) |
+                    (!!(s->rtc.tm.tm_hour >= 12) << 7);	/* PM_nAM */
+        else
+            return to_bcd(s->rtc.tm.tm_hour);
+    case MENELAUS_RTC_DAY:
+        menelaus_rtc_update(s);
+        return to_bcd(s->rtc.tm.tm_mday);
+    case MENELAUS_RTC_MON:
+        menelaus_rtc_update(s);
+        return to_bcd(s->rtc.tm.tm_mon + 1);
+    case MENELAUS_RTC_YR:
+        menelaus_rtc_update(s);
+        return to_bcd(s->rtc.tm.tm_year - 2000);
+    case MENELAUS_RTC_WKDAY:
+        menelaus_rtc_update(s);
+        return to_bcd(s->rtc.tm.tm_wday);
+    case MENELAUS_RTC_AL_SEC:
+        return to_bcd(s->rtc.alm.tm_sec);
+    case MENELAUS_RTC_AL_MIN:
+        return to_bcd(s->rtc.alm.tm_min);
+    case MENELAUS_RTC_AL_HR:
+        if ((s->rtc.ctrl >> 2) & 1)			/* MODE12_n24 */
+            return to_bcd((s->rtc.alm.tm_hour % 12) + 1) |
+                    (!!(s->rtc.alm.tm_hour >= 12) << 7);/* AL_PM_nAM */
+        else
+            return to_bcd(s->rtc.alm.tm_hour);
+    case MENELAUS_RTC_AL_DAY:
+        return to_bcd(s->rtc.alm.tm_mday);
+    case MENELAUS_RTC_AL_MON:
+        return to_bcd(s->rtc.alm.tm_mon + 1);
+    case MENELAUS_RTC_AL_YR:
+        return to_bcd(s->rtc.alm.tm_year - 2000);
+    case MENELAUS_RTC_COMP_MSB:
+        return (s->rtc.comp >> 8) & 0xff;
+    case MENELAUS_RTC_COMP_LSB:
+        return (s->rtc.comp >> 0) & 0xff;
+
+    case MENELAUS_S1_PULL_EN:
+        return s->pull[0];
+    case MENELAUS_S1_PULL_DIR:
+        return s->pull[1];
+    case MENELAUS_S2_PULL_EN:
+        return s->pull[2];
+    case MENELAUS_S2_PULL_DIR:
+        return s->pull[3];
+
+    case MENELAUS_MCT_CTRL3: reg ++;
+    case MENELAUS_MCT_CTRL2: reg ++;
+    case MENELAUS_MCT_CTRL1:
+        return s->mmc_ctrl[reg];
+    case MENELAUS_MCT_PIN_ST:
+        /* TODO: return the real Card Detect */
+        return 0;
+    case MENELAUS_DEBOUNCE1:
+        return s->mmc_debounce;
+
+    default:
+#ifdef VERBOSE
+        printf("%s: unknown register %02x\n", __FUNCTION__, addr);
+#endif
+        break;
+    }
+    return 0;
+}
+
+static void menelaus_write(void *opaque, uint8_t addr, uint8_t value)
+{
+    struct menelaus_s *s = (struct menelaus_s *) opaque;
+    int line;
+    int reg = 0;
+    struct tm tm;
+
+    switch (addr) {
+    case MENELAUS_VCORE_CTRL1:
+        s->vcore[0] = (value & 0xe) | MIN(value & 0x1f, 0x12);
+        break;
+    case MENELAUS_VCORE_CTRL2:
+        s->vcore[1] = value;
+        break;
+    case MENELAUS_VCORE_CTRL3:
+        s->vcore[2] = MIN(value & 0x1f, 0x12);
+        break;
+    case MENELAUS_VCORE_CTRL4:
+        s->vcore[3] = MIN(value & 0x1f, 0x12);
+        break;
+    case MENELAUS_VCORE_CTRL5:
+        s->vcore[4] = value & 3;
+        /* XXX
+         * auto set to 3 on M_Active, nRESWARM
+         * auto set to 0 on M_WaitOn, M_Backup
+         */
+        break;
+
+    case MENELAUS_DCDC_CTRL1:
+        s->dcdc[0] = value & 0x3f;
+        break;
+    case MENELAUS_DCDC_CTRL2:
+        s->dcdc[1] = value & 0x07;
+        /* XXX
+         * auto set to 3 on M_Active, nRESWARM
+         * auto set to 0 on M_WaitOn, M_Backup
+         */
+        break;
+    case MENELAUS_DCDC_CTRL3:
+        s->dcdc[2] = value & 0x07;
+        break;
+
+    case MENELAUS_LDO_CTRL1:
+        s->ldo[0] = value;
+        break;
+    case MENELAUS_LDO_CTRL2:
+        s->ldo[1] = value & 0x7f;
+        /* XXX
+         * auto set to 0x7e on M_WaitOn, M_Backup
+         */
+        break;
+    case MENELAUS_LDO_CTRL3:
+        s->ldo[2] = value & 3;
+        /* XXX
+         * auto set to 3 on M_Active, nRESWARM
+         * auto set to 0 on M_WaitOn, M_Backup
+         */
+        break;
+    case MENELAUS_LDO_CTRL4:
+        s->ldo[3] = value & 3;
+        /* XXX
+         * auto set to 3 on M_Active, nRESWARM
+         * auto set to 0 on M_WaitOn, M_Backup
+         */
+        break;
+    case MENELAUS_LDO_CTRL5:
+        s->ldo[4] = value & 3;
+        /* XXX
+         * auto set to 3 on M_Active, nRESWARM
+         * auto set to 0 on M_WaitOn, M_Backup
+         */
+        break;
+    case MENELAUS_LDO_CTRL6:
+        s->ldo[5] = value & 3;
+        break;
+    case MENELAUS_LDO_CTRL7:
+        s->ldo[6] = value & 3;
+        break;
+    case MENELAUS_LDO_CTRL8:
+        s->ldo[7] = value & 3;
+        break;
+
+    case MENELAUS_SLEEP_CTRL2: reg ++;
+    case MENELAUS_SLEEP_CTRL1:
+        s->sleep[reg] = value;
+        break;
+
+    case MENELAUS_DEVICE_OFF:
+        if (value & 1)
+            menelaus_reset(&s->i2c);
+        break;
+
+    case MENELAUS_OSC_CTRL:
+        s->osc = value & 7;
+        break;
+
+    case MENELAUS_DETECT_CTRL:
+        s->detect = value & 0x7f;
+        break;
+
+    case MENELAUS_INT_MASK1:
+        s->mask &= 0xf00;
+        s->mask |= value << 0;
+        menelaus_update(s);
+        break;
+    case MENELAUS_INT_MASK2:
+        s->mask &= 0x0ff;
+        s->mask |= value << 8;
+        menelaus_update(s);
+        break;
+
+    case MENELAUS_INT_ACK1:
+        s->status &= ~(((uint16_t) value) << 0);
+        menelaus_update(s);
+        break;
+    case MENELAUS_INT_ACK2:
+        s->status &= ~(((uint16_t) value) << 8);
+        menelaus_update(s);
+        break;
+
+    case MENELAUS_GPIO_CTRL:
+        for (line = 0; line < 3; line ++)
+            if (((s->dir ^ value) >> line) & 1)
+                if (s->handler[line])
+                    qemu_set_irq(s->handler[line],
+                                    ((s->outputs & ~s->dir) >> line) & 1);
+        s->dir = value & 0x67;
+        break;
+    case MENELAUS_GPIO_OUT:
+        for (line = 0; line < 3; line ++)
+            if ((((s->outputs ^ value) & ~s->dir) >> line) & 1)
+                if (s->handler[line])
+                    qemu_set_irq(s->handler[line], (s->outputs >> line) & 1);
+        s->outputs = value & 0x07;
+        break;
+
+    case MENELAUS_BBSMS:
+        s->bbsms = 0x0d;
+        break;
+
+    case MENELAUS_RTC_CTRL:
+        if ((s->rtc.ctrl ^ value) & 1) {			/* RTC_EN */
+            if (value & 1)
+                menelaus_rtc_start(s);
+            else
+                menelaus_rtc_stop(s);
+        }
+        s->rtc.ctrl = value & 0x1f;
+        menelaus_alm_update(s);
+        break;
+    case MENELAUS_RTC_UPDATE:
+        menelaus_rtc_update(s);
+        memcpy(&tm, &s->rtc.tm, sizeof(tm));
+        switch (value & 0xf) {
+        case 0:
+            break;
+        case 1:
+            tm.tm_sec = s->rtc.new.tm_sec;
+            break;
+        case 2:
+            tm.tm_min = s->rtc.new.tm_min;
+            break;
+        case 3:
+            if (s->rtc.new.tm_hour > 23)
+                goto rtc_badness;
+            tm.tm_hour = s->rtc.new.tm_hour;
+            break;
+        case 4:
+            if (s->rtc.new.tm_mday < 1)
+                goto rtc_badness;
+            /* TODO check range */
+            tm.tm_mday = s->rtc.new.tm_mday;
+            break;
+        case 5:
+            if (s->rtc.new.tm_mon < 0 || s->rtc.new.tm_mon > 11)
+                goto rtc_badness;
+            tm.tm_mon = s->rtc.new.tm_mon;
+            break;
+        case 6:
+            tm.tm_year = s->rtc.new.tm_year;
+            break;
+        case 7:
+            /* TODO set .tm_mday instead */
+            tm.tm_wday = s->rtc.new.tm_wday;
+            break;
+        case 8:
+            if (s->rtc.new.tm_hour > 23)
+                goto rtc_badness;
+            if (s->rtc.new.tm_mday < 1)
+                goto rtc_badness;
+            if (s->rtc.new.tm_mon < 0 || s->rtc.new.tm_mon > 11)
+                goto rtc_badness;
+            tm.tm_sec = s->rtc.new.tm_sec;
+            tm.tm_min = s->rtc.new.tm_min;
+            tm.tm_hour = s->rtc.new.tm_hour;
+            tm.tm_mday = s->rtc.new.tm_mday;
+            tm.tm_mon = s->rtc.new.tm_mon;
+            tm.tm_year = s->rtc.new.tm_year;
+            break;
+        rtc_badness:
+        default:
+            fprintf(stderr, "%s: bad RTC_UPDATE value %02x\n",
+                            __FUNCTION__, value);
+            s->status |= 1 << 10;				/* RTCERR */
+            menelaus_update(s);
+        }
+        s->rtc.sec += difftime(mktime(&tm), mktime(&s->rtc.tm));
+        break;
+    case MENELAUS_RTC_SEC:
+        s->rtc.tm.tm_sec = from_bcd(value & 0x7f);
+        break;
+    case MENELAUS_RTC_MIN:
+        s->rtc.tm.tm_min = from_bcd(value & 0x7f);
+        break;
+    case MENELAUS_RTC_HR:
+        s->rtc.tm.tm_hour = (s->rtc.ctrl & (1 << 2)) ?	/* MODE12_n24 */
+                MIN(from_bcd(value & 0x3f), 12) + ((value >> 7) ? 11 : -1) :
+                from_bcd(value & 0x3f);
+        break;
+    case MENELAUS_RTC_DAY:
+        s->rtc.tm.tm_mday = from_bcd(value);
+        break;
+    case MENELAUS_RTC_MON:
+        s->rtc.tm.tm_mon = MAX(1, from_bcd(value)) - 1;
+        break;
+    case MENELAUS_RTC_YR:
+        s->rtc.tm.tm_year = 2000 + from_bcd(value);
+        break;
+    case MENELAUS_RTC_WKDAY:
+        s->rtc.tm.tm_mday = from_bcd(value);
+        break;
+    case MENELAUS_RTC_AL_SEC:
+        s->rtc.alm.tm_sec = from_bcd(value & 0x7f);
+        menelaus_alm_update(s);
+        break;
+    case MENELAUS_RTC_AL_MIN:
+        s->rtc.alm.tm_min = from_bcd(value & 0x7f);
+        menelaus_alm_update(s);
+        break;
+    case MENELAUS_RTC_AL_HR:
+        s->rtc.alm.tm_hour = (s->rtc.ctrl & (1 << 2)) ?	/* MODE12_n24 */
+                MIN(from_bcd(value & 0x3f), 12) + ((value >> 7) ? 11 : -1) :
+                from_bcd(value & 0x3f);
+        menelaus_alm_update(s);
+        break;
+    case MENELAUS_RTC_AL_DAY:
+        s->rtc.alm.tm_mday = from_bcd(value);
+        menelaus_alm_update(s);
+        break;
+    case MENELAUS_RTC_AL_MON:
+        s->rtc.alm.tm_mon = MAX(1, from_bcd(value)) - 1;
+        menelaus_alm_update(s);
+        break;
+    case MENELAUS_RTC_AL_YR:
+        s->rtc.alm.tm_year = 2000 + from_bcd(value);
+        menelaus_alm_update(s);
+        break;
+    case MENELAUS_RTC_COMP_MSB:
+        s->rtc.comp &= 0xff;
+        s->rtc.comp |= value << 8;
+        break;
+    case MENELAUS_RTC_COMP_LSB:
+        s->rtc.comp &= 0xff << 8;
+        s->rtc.comp |= value;
+        break;
+
+    case MENELAUS_S1_PULL_EN:
+        s->pull[0] = value;
+        break;
+    case MENELAUS_S1_PULL_DIR:
+        s->pull[1] = value & 0x1f;
+        break;
+    case MENELAUS_S2_PULL_EN:
+        s->pull[2] = value;
+        break;
+    case MENELAUS_S2_PULL_DIR:
+        s->pull[3] = value & 0x1f;
+        break;
+
+    case MENELAUS_MCT_CTRL1:
+        s->mmc_ctrl[0] = value & 0x7f;
+        break;
+    case MENELAUS_MCT_CTRL2:
+        s->mmc_ctrl[1] = value;
+        /* TODO update Card Detect interrupts */
+        break;
+    case MENELAUS_MCT_CTRL3:
+        s->mmc_ctrl[2] = value & 0xf;
+        break;
+    case MENELAUS_DEBOUNCE1:
+        s->mmc_debounce = value & 0x3f;
+        break;
+
+    default:
+#ifdef VERBOSE
+        printf("%s: unknown register %02x\n", __FUNCTION__, addr);
+#endif
+    }
+}
+
+static void menelaus_event(i2c_slave *i2c, enum i2c_event event)
+{
+    struct menelaus_s *s = (struct menelaus_s *) i2c;
+
+    if (event == I2C_START_SEND)
+        s->firstbyte = 1;
+}
+
+static int menelaus_tx(i2c_slave *i2c, uint8_t data)
+{
+    struct menelaus_s *s = (struct menelaus_s *) i2c;
+    /* Interpret register address byte */
+    if (s->firstbyte) {
+        s->reg = data;
+        s->firstbyte = 0;
+    } else
+        menelaus_write(s, s->reg ++, data);
+
+    return 0;
+}
+
+static int menelaus_rx(i2c_slave *i2c)
+{
+    struct menelaus_s *s = (struct menelaus_s *) i2c;
+
+    return menelaus_read(s, s->reg ++);
+}
+
+static void tm_put(QEMUFile *f, struct tm *tm) {
+    qemu_put_be16(f, tm->tm_sec);
+    qemu_put_be16(f, tm->tm_min);
+    qemu_put_be16(f, tm->tm_hour);
+    qemu_put_be16(f, tm->tm_mday);
+    qemu_put_be16(f, tm->tm_min);
+    qemu_put_be16(f, tm->tm_year);
+}
+
+static void tm_get(QEMUFile *f, struct tm *tm) {
+    tm->tm_sec = qemu_get_be16(f);
+    tm->tm_min = qemu_get_be16(f);
+    tm->tm_hour = qemu_get_be16(f);
+    tm->tm_mday = qemu_get_be16(f);
+    tm->tm_min = qemu_get_be16(f);
+    tm->tm_year = qemu_get_be16(f);
+}
+
+static void menelaus_save(QEMUFile *f, void *opaque)
+{
+    struct menelaus_s *s = (struct menelaus_s *) opaque;
+
+    qemu_put_be32(f, s->firstbyte);
+    qemu_put_8s(f, &s->reg);
+
+    qemu_put_8s(f, &s->vcore[0]);
+    qemu_put_8s(f, &s->vcore[1]);
+    qemu_put_8s(f, &s->vcore[2]);
+    qemu_put_8s(f, &s->vcore[3]);
+    qemu_put_8s(f, &s->vcore[4]);
+    qemu_put_8s(f, &s->dcdc[3]);
+    qemu_put_8s(f, &s->dcdc[3]);
+    qemu_put_8s(f, &s->dcdc[3]);
+    qemu_put_8s(f, &s->ldo[0]);
+    qemu_put_8s(f, &s->ldo[1]);
+    qemu_put_8s(f, &s->ldo[2]);
+    qemu_put_8s(f, &s->ldo[3]);
+    qemu_put_8s(f, &s->ldo[4]);
+    qemu_put_8s(f, &s->ldo[5]);
+    qemu_put_8s(f, &s->ldo[6]);
+    qemu_put_8s(f, &s->ldo[7]);
+    qemu_put_8s(f, &s->sleep[0]);
+    qemu_put_8s(f, &s->sleep[1]);
+    qemu_put_8s(f, &s->osc);
+    qemu_put_8s(f, &s->detect);
+    qemu_put_be16s(f, &s->mask);
+    qemu_put_be16s(f, &s->status);
+    qemu_put_8s(f, &s->dir);
+    qemu_put_8s(f, &s->inputs);
+    qemu_put_8s(f, &s->outputs);
+    qemu_put_8s(f, &s->bbsms);
+    qemu_put_8s(f, &s->pull[0]);
+    qemu_put_8s(f, &s->pull[1]);
+    qemu_put_8s(f, &s->pull[2]);
+    qemu_put_8s(f, &s->pull[3]);
+    qemu_put_8s(f, &s->mmc_ctrl[0]);
+    qemu_put_8s(f, &s->mmc_ctrl[1]);
+    qemu_put_8s(f, &s->mmc_ctrl[2]);
+    qemu_put_8s(f, &s->mmc_debounce);
+    qemu_put_8s(f, &s->rtc.ctrl);
+    qemu_put_be16s(f, &s->rtc.comp);
+    /* Should be <= 1000 */
+    qemu_put_be16(f, s->rtc.next - qemu_get_clock(rt_clock));
+    tm_put(f, &s->rtc.new);
+    tm_put(f, &s->rtc.alm);
+    qemu_put_byte(f, s->pwrbtn_state);
+
+    i2c_slave_save(f, &s->i2c);
+}
+
+static int menelaus_load(QEMUFile *f, void *opaque, int version_id)
+{
+    struct menelaus_s *s = (struct menelaus_s *) opaque;
+
+    s->firstbyte = qemu_get_be32(f);
+    qemu_get_8s(f, &s->reg);
+
+    if (s->rtc.ctrl & 1)					/* RTC_EN */
+        menelaus_rtc_stop(s);
+    qemu_get_8s(f, &s->vcore[0]);
+    qemu_get_8s(f, &s->vcore[1]);
+    qemu_get_8s(f, &s->vcore[2]);
+    qemu_get_8s(f, &s->vcore[3]);
+    qemu_get_8s(f, &s->vcore[4]);
+    qemu_get_8s(f, &s->dcdc[3]);
+    qemu_get_8s(f, &s->dcdc[3]);
+    qemu_get_8s(f, &s->dcdc[3]);
+    qemu_get_8s(f, &s->ldo[0]);
+    qemu_get_8s(f, &s->ldo[1]);
+    qemu_get_8s(f, &s->ldo[2]);
+    qemu_get_8s(f, &s->ldo[3]);
+    qemu_get_8s(f, &s->ldo[4]);
+    qemu_get_8s(f, &s->ldo[5]);
+    qemu_get_8s(f, &s->ldo[6]);
+    qemu_get_8s(f, &s->ldo[7]);
+    qemu_get_8s(f, &s->sleep[0]);
+    qemu_get_8s(f, &s->sleep[1]);
+    qemu_get_8s(f, &s->osc);
+    qemu_get_8s(f, &s->detect);
+    qemu_get_be16s(f, &s->mask);
+    qemu_get_be16s(f, &s->status);
+    qemu_get_8s(f, &s->dir);
+    qemu_get_8s(f, &s->inputs);
+    qemu_get_8s(f, &s->outputs);
+    qemu_get_8s(f, &s->bbsms);
+    qemu_get_8s(f, &s->pull[0]);
+    qemu_get_8s(f, &s->pull[1]);
+    qemu_get_8s(f, &s->pull[2]);
+    qemu_get_8s(f, &s->pull[3]);
+    qemu_get_8s(f, &s->mmc_ctrl[0]);
+    qemu_get_8s(f, &s->mmc_ctrl[1]);
+    qemu_get_8s(f, &s->mmc_ctrl[2]);
+    qemu_get_8s(f, &s->mmc_debounce);
+    qemu_get_8s(f, &s->rtc.ctrl);
+    qemu_get_be16s(f, &s->rtc.comp);
+    s->rtc.next = qemu_get_be16(f);
+    tm_get(f, &s->rtc.new);
+    tm_get(f, &s->rtc.alm);
+    s->pwrbtn_state = qemu_get_byte(f);
+    menelaus_alm_update(s);
+    menelaus_update(s);
+    if (s->rtc.ctrl & 1)					/* RTC_EN */
+        menelaus_rtc_start(s);
+
+    i2c_slave_load(f, &s->i2c);
+    return 0;
+}
+
+static int menelaus_iid = 0;
+
+i2c_slave *twl92230_init(i2c_bus *bus, qemu_irq irq)
+{
+    struct menelaus_s *s = (struct menelaus_s *)
+            i2c_slave_init(bus, 0, sizeof(struct menelaus_s));
+
+    s->i2c.event = menelaus_event;
+    s->i2c.recv = menelaus_rx;
+    s->i2c.send = menelaus_tx;
+
+    /* TODO: use the qemu gettime functions */
+    s->rtc.gettime = localtime_r;
+
+    s->irq = irq;
+    s->rtc.hz = qemu_new_timer(rt_clock, menelaus_rtc_hz, s);
+    s->in = qemu_allocate_irqs(menelaus_gpio_set, s, 3);
+    s->pwrbtn = qemu_allocate_irqs(menelaus_pwrbtn_set, s, 1)[0];
+
+    menelaus_reset(&s->i2c);
+
+    register_savevm("menelaus", menelaus_iid ++,
+                    0, menelaus_save, menelaus_load, s);
+
+    return &s->i2c;
+}
+
+qemu_irq *twl92230_gpio_in_get(i2c_slave *i2c)
+{
+    struct menelaus_s *s = (struct menelaus_s *) i2c;
+
+    return s->in;
+}
+
+void twl92230_gpio_out_set(i2c_slave *i2c, int line, qemu_irq handler)
+{
+    struct menelaus_s *s = (struct menelaus_s *) i2c;
+
+    if (line >= 3 || line < 0) {
+        fprintf(stderr, "%s: No GPO line %i\n", __FUNCTION__, line);
+        exit(-1);
+    }
+    s->handler[line] = handler;
+}
diff --git a/hw/versatilepb.c b/hw/versatilepb.c
index da6e4ec..ecc0037 100644
--- a/hw/versatilepb.c
+++ b/hw/versatilepb.c
@@ -157,6 +157,8 @@ static qemu_irq *vpb_sic_init(uint32_t base, qemu_irq *parent, int irq)
    peripherans 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(int ram_size, int vga_ram_size,
                      const char *boot_device, DisplayState *ds,
                      const char *kernel_filename, const char *kernel_cmdline,
@@ -283,8 +285,12 @@ static void versatile_init(int ram_size, int vga_ram_size,
     /*  0x101f3000 UART2.  */
     /* 0x101f4000 SSPI.  */
 
-    arm_load_kernel(env, ram_size, kernel_filename, kernel_cmdline,
-                    initrd_filename, board_id, 0x0);
+    versatile_binfo.ram_size = ram_size;
+    versatile_binfo.kernel_filename = kernel_filename;
+    versatile_binfo.kernel_cmdline = kernel_cmdline;
+    versatile_binfo.initrd_filename = initrd_filename;
+    versatile_binfo.board_id = board_id;
+    arm_load_kernel(env, &versatile_binfo);
 }
 
 static void vpb_init(int ram_size, int vga_ram_size,
diff --git a/softmmu_template.h b/softmmu_template.h
index 0a4bc7e..d480f34 100644
--- a/softmmu_template.h
+++ b/softmmu_template.h
@@ -51,12 +51,15 @@ static DATA_TYPE glue(glue(slow_ld, SUFFIX), MMUSUFFIX)(target_ulong addr,
                                                         int mmu_idx,
                                                         void *retaddr);
 static inline DATA_TYPE glue(io_read, SUFFIX)(target_phys_addr_t physaddr,
-                                              target_ulong tlb_addr)
+                                              target_ulong tlb_addr,
+                                              target_ulong tlb_io)
 {
     DATA_TYPE res;
     int index;
 
-    index = (tlb_addr >> IO_MEM_SHIFT) & (IO_MEM_NB_ENTRIES - 1);
+    index = (tlb_addr & ~TARGET_PAGE_MASK) >> IO_MEM_SHIFT;
+    if (index > 4)
+        index = (tlb_io >> IO_MEM_SHIFT) & (IO_MEM_NB_ENTRIES - 1);
 #if SHIFT <= 2
     res = io_mem_read[index][SHIFT](io_mem_opaque[index], physaddr);
 #else
@@ -95,7 +98,9 @@ DATA_TYPE REGPARM glue(glue(__ld, SUFFIX), MMUSUFFIX)(target_ulong addr,
             /* IO access */
             if ((addr & (DATA_SIZE - 1)) != 0)
                 goto do_unaligned_access;
-            res = glue(io_read, SUFFIX)(physaddr, tlb_addr);
+            res = glue(io_read, SUFFIX)(physaddr, tlb_addr,
+                                        env->tlb_table[mmu_idx]
+                                        [index].addr_code);
         } else if (((addr & ~TARGET_PAGE_MASK) + DATA_SIZE - 1) >= TARGET_PAGE_SIZE) {
             /* slow unaligned access (it spans two pages or IO) */
         do_unaligned_access:
@@ -147,7 +152,9 @@ static DATA_TYPE glue(glue(slow_ld, SUFFIX), MMUSUFFIX)(target_ulong addr,
             /* IO access */
             if ((addr & (DATA_SIZE - 1)) != 0)
                 goto do_unaligned_access;
-            res = glue(io_read, SUFFIX)(physaddr, tlb_addr);
+            res = glue(io_read, SUFFIX)(physaddr, tlb_addr,
+                                        env->tlb_table[mmu_idx]
+                                        [index].addr_code);
         } else if (((addr & ~TARGET_PAGE_MASK) + DATA_SIZE - 1) >= TARGET_PAGE_SIZE) {
         do_unaligned_access:
             /* slow unaligned access (it spans two pages) */
@@ -186,11 +193,14 @@ static void glue(glue(slow_st, SUFFIX), MMUSUFFIX)(target_ulong addr,
 static inline void glue(io_write, SUFFIX)(target_phys_addr_t physaddr,
                                           DATA_TYPE val,
                                           target_ulong tlb_addr,
-                                          void *retaddr)
+                                          void *retaddr,
+                                          target_ulong tlb_io)
 {
     int index;
 
-    index = (tlb_addr >> IO_MEM_SHIFT) & (IO_MEM_NB_ENTRIES - 1);
+    index = (tlb_addr & ~TARGET_PAGE_MASK) >> IO_MEM_SHIFT;
+    if (index > 4)
+        index = (tlb_io >> IO_MEM_SHIFT) & (IO_MEM_NB_ENTRIES - 1);
     env->mem_write_vaddr = tlb_addr;
     env->mem_write_pc = (unsigned long)retaddr;
 #if SHIFT <= 2
@@ -228,7 +238,8 @@ void REGPARM glue(glue(__st, SUFFIX), MMUSUFFIX)(target_ulong addr,
             if ((addr & (DATA_SIZE - 1)) != 0)
                 goto do_unaligned_access;
             retaddr = GETPC();
-            glue(io_write, SUFFIX)(physaddr, val, tlb_addr, retaddr);
+            glue(io_write, SUFFIX)(physaddr, val, tlb_addr, retaddr,
+                                   env->tlb_table[mmu_idx][index].addr_code);
         } else if (((addr & ~TARGET_PAGE_MASK) + DATA_SIZE - 1) >= TARGET_PAGE_SIZE) {
         do_unaligned_access:
             retaddr = GETPC();
@@ -278,7 +289,8 @@ static void glue(glue(slow_st, SUFFIX), MMUSUFFIX)(target_ulong addr,
             /* IO access */
             if ((addr & (DATA_SIZE - 1)) != 0)
                 goto do_unaligned_access;
-            glue(io_write, SUFFIX)(physaddr, val, tlb_addr, retaddr);
+            glue(io_write, SUFFIX)(physaddr, val, tlb_addr, retaddr,
+                                   env->tlb_table[mmu_idx][index].addr_code);
         } else if (((addr & ~TARGET_PAGE_MASK) + DATA_SIZE - 1) >= TARGET_PAGE_SIZE) {
         do_unaligned_access:
             /* XXX: not efficient, but simple */
diff --git a/target-arm/cpu.h b/target-arm/cpu.h
index b284a21..633b335 100644
--- a/target-arm/cpu.h
+++ b/target-arm/cpu.h
@@ -198,12 +198,16 @@ typedef struct CPUARMState {
     CPU_COMMON
 
     /* These fields after the common ones so they are preserved on reset.  */
-    int ram_size;
-    const char *kernel_filename;
-    const char *kernel_cmdline;
-    const char *initrd_filename;
-    int board_id;
-    target_phys_addr_t loader_start;
+    struct arm_boot_info {
+        int ram_size;
+        const char *kernel_filename;
+        const char *kernel_cmdline;
+        const char *initrd_filename;
+        target_phys_addr_t loader_start;
+        int nb_cpus;
+        int board_id;
+        int (*atag_board)(struct arm_boot_info *info, void *p);
+    } *boot_info;
 } CPUARMState;
 
 CPUARMState *cpu_arm_init(const char *cpu_model);
@@ -377,6 +381,7 @@ void cpu_arm_set_cp_io(CPUARMState *env, int cpnum,
 #define ARM_CPUID_PXA270_C0   0x69054114
 #define ARM_CPUID_PXA270_C5   0x69054117
 #define ARM_CPUID_ARM1136     0x4117b363
+#define ARM_CPUID_ARM1136_R2  0x4107b362
 #define ARM_CPUID_ARM11MPCORE 0x410fb022
 #define ARM_CPUID_CORTEXA8    0x410fc080
 #define ARM_CPUID_CORTEXM3    0x410fc231
diff --git a/target-arm/helper.c b/target-arm/helper.c
index 86470db..0709129 100644
--- a/target-arm/helper.c
+++ b/target-arm/helper.c
@@ -53,6 +53,9 @@ static void cpu_reset_model_id(CPUARMState *env, uint32_t id)
         env->cp15.c0_cachetype = 0x1dd20d2;
         env->cp15.c1_sys = 0x00090078;
         break;
+    case ARM_CPUID_ARM1136_R2:
+        /* TODO! */
+        env->GE = 0x5;
     case ARM_CPUID_ARM1136:
         set_feature(env, ARM_FEATURE_V6);
         set_feature(env, ARM_FEATURE_VFP);
@@ -198,6 +201,7 @@ static const struct arm_cpu_t arm_cpu_names[] = {
     { ARM_CPUID_ARM946, "arm946"},
     { ARM_CPUID_ARM1026, "arm1026"},
     { ARM_CPUID_ARM1136, "arm1136"},
+    { ARM_CPUID_ARM1136_R2, "arm1136-r2"},
     { ARM_CPUID_ARM11MPCORE, "arm11mpcore"},
     { ARM_CPUID_CORTEXM3, "cortex-m3"},
     { ARM_CPUID_CORTEXA8, "cortex-a8"},
@@ -1539,6 +1543,7 @@ uint32_t helper_get_cp15(CPUState *env, uint32_t insn)
             case ARM_CPUID_ARM1026:
                 return 1;
             case ARM_CPUID_ARM1136:
+            case ARM_CPUID_ARM1136_R2:
                 return 7;
             case ARM_CPUID_ARM11MPCORE:
                 return 1;
@@ -1721,6 +1726,10 @@ uint32_t helper_get_cp15(CPUState *env, uint32_t insn)
             case 8: /* TI925T_status */
                 return 0;
             }
+            /* TODO: Peripheral port remap register:
+             * On OMAP2 mcr p15, 0, rn, c15, c2, 4 sets up the interrupt
+             * controller base address at $rn & ~0xfff and map size of
+             * 0x200 << ($rn & 0xfff), when MMU is off.  */
             goto bad_reg;
         }
         return 0;
diff --git a/vl.c b/vl.c
index d371af7..76d8def 100644
--- a/vl.c
+++ b/vl.c
@@ -8006,6 +8006,7 @@ static void register_machines(void)
     qemu_register_machine(&borzoipda_machine);
     qemu_register_machine(&terrierpda_machine);
     qemu_register_machine(&palmte_machine);
+    qemu_register_machine(&n800_machine);
     qemu_register_machine(&lm3s811evb_machine);
     qemu_register_machine(&lm3s6965evb_machine);
     qemu_register_machine(&connex_machine);
