Nokia N800 machine support (ARM).

Also add various peripherals: two miscellaneous Nokia CBUS chips,
EPSON S1D13745 LCD/TV remote-framebuffer controller,
TWL92230 - standard OMAP2 power management companion chip on i2c.
Generic OneNAND flash memory,
TMP105 temperature sensor on i2c.


git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@4215 c046a42c-6fe2-441c-8c8c-71466251a162
diff --git a/hw/blizzard.c b/hw/blizzard.c
new file mode 100644
index 0000000..9ad7e6a
--- /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 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, 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..66aa939
--- /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 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, 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 3a54245..42a4437 100644
--- a/hw/boards.h
+++ b/hw/boards.h
@@ -81,6 +81,9 @@
 /* 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..c84de05
--- /dev/null
+++ b/hw/cbus.c
@@ -0,0 +1,624 @@
+/*
+ * CBUS three-pin bus and the Retu / Betty / Tahvo / Vilma / Avilma /
+ * Hinku / Vinku / Ahne / Pihi chips used in various Nokia platforms.
+ * Based on reverse-engineering of a linux driver.
+ *
+ * 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 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, 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;
+    uint16_t status;
+
+    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) Audio 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 button */
+    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 0x0215 | (s->is_vilma << 7);
+
+    case RETU_REG_IDR:	/* TODO: Or is this ffs(s->irqst)?  */
+        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:
+        /* TODO */
+        return 0x0000;
+
+    case RETU_REG_STATUS:
+        return s->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:
+        /* 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->status = 0x0020;
+    s->is_vilma = !!vilma;
+    s->rtc.cal = 0x01;
+    s->result[retu_adc_bsi] = 0x3c2;
+    s->result[retu_adc_batt_temp] = 0x0fc;
+    s->result[retu_adc_chg_volt] = 0x165;
+    s->result[retu_adc_head_det] = 123;
+    s->result[retu_adc_hook_det] = 1023;
+    s->result[retu_adc_rf_gp] = 0x11;
+    s->result[retu_adc_tx_det] = 0x11;
+    s->result[retu_adc_batt_volt] = 0x250;
+    s->result[retu_adc_sens] = 2;
+    s->result[retu_adc_sens_temp] = 0x11;
+    s->result[retu_adc_bbatt_volt] = 0x3d0;
+    s->result[retu_adc_self_temp] = 0x330;
+
+    s->cbus.opaque = s;
+    s->cbus.io = retu_io;
+    s->cbus.addr = 1;
+
+    return &s->cbus;
+}
+
+void retu_key_event(void *retu, int state)
+{
+    struct cbus_slave_s *slave = (struct cbus_slave_s *) retu;
+    struct cbus_retu_s *s = (struct cbus_retu_s *) slave->opaque;
+
+    s->irqst |= 1 << retu_int_pwr;
+    retu_interrupt_update(s);
+
+    if (state)
+        s->status &= ~(1 << 5);
+    else
+        s->status |= 1 << 5;
+}
+
+void retu_head_event(void *retu, int state)
+{
+    struct cbus_slave_s *slave = (struct cbus_slave_s *) retu;
+    struct cbus_retu_s *s = (struct cbus_retu_s *) slave->opaque;
+
+    if ((s->cc[0] & 0x500) == 0x500) {	/* TODO: Which bits? */
+        /* TODO: reissue the interrupt every 100ms or so.  */
+        s->irqst |= 1 << retu_int_head;
+        retu_interrupt_update(s);
+    }
+
+    if (state)
+        s->result[retu_adc_head_det] = 50;
+    else
+        s->result[retu_adc_head_det] = 123;
+}
+
+void retu_hook_event(void *retu, int state)
+{
+    struct cbus_slave_s *slave = (struct cbus_slave_s *) retu;
+    struct cbus_retu_s *s = (struct cbus_retu_s *) slave->opaque;
+
+    if ((s->cc[0] & 0x500) == 0x500) {
+        /* TODO: reissue the interrupt every 100ms or so.  */
+        s->irqst |= 1 << retu_int_hook;
+        retu_interrupt_update(s);
+    }
+
+    if (state)
+        s->result[retu_adc_hook_det] = 50;
+    else
+        s->result[retu_adc_hook_det] = 123;
+}
+
+/* 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);	/* 22 in N810 */
+
+    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 2c3f83d..e2ecdf9 100644
--- a/hw/devices.h
+++ b/hw/devices.h
@@ -31,4 +31,25 @@
 /* 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);
+
+void retu_key_event(void *retu, int state);
+
 #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 @@
 #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 @@
 /* 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/nseries.c b/hw/nseries.c
new file mode 100644
index 0000000..31906c1
--- /dev/null
+++ b/hw/nseries.c
@@ -0,0 +1,918 @@
+/*
+ * Nokia N-series internet tablets.
+ *
+ * Copyright (C) 2007 Nokia Corporation
+ * Written by Andrzej Zaborowski <andrew@openedhand.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, 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 N8x0 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];
+
+    void *retu;
+    void *tahvo;
+};
+
+/* GPIO pins */
+#define N800_TUSB_ENABLE_GPIO		0
+#define N800_MMC2_WP_GPIO		8
+#define N800_UNKNOWN_GPIO0		9	/* out */
+#define N800_UNKNOWN_GPIO1		10	/* out */
+#define N800_CAM_TURN_GPIO		12
+#define N800_BLIZZARD_POWERDOWN_GPIO	15
+#define N800_MMC1_WP_GPIO		23
+#define N8X0_ONENAND_GPIO		26
+#define N800_UNKNOWN_GPIO2		53	/* out */
+#define N8X0_TUSB_INT_GPIO		58
+#define N800_BT_WKUP_GPIO		61
+#define N800_STI_GPIO			62
+#define N8X0_CBUS_SEL_GPIO		64
+#define N8X0_CBUS_CLK_GPIO		65	/* sure? */
+#define N8X0_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 N8X0_BT_HOST_WKUP_GPIO		98
+#define N800_UNKNOWN_GPIO3		101	/* out */
+#define N810_KB_LOCK_GPIO		102
+#define N800_TSC_TS_GPIO		103
+#define N810_TSC2005_GPIO		106
+#define N800_HEADPHONE_GPIO		107
+#define N8X0_RETU_GPIO			108
+#define N800_TSC_KP_IRQ_GPIO		109
+#define N810_KEYBOARD_GPIO		109
+#define N800_BAT_COVER_GPIO		110
+#define N810_SLIDE_GPIO			110
+#define N8X0_TAHVO_GPIO			111
+#define N800_UNKNOWN_GPIO4		112	/* out */
+#define N810_TSC_RESET_GPIO		118
+#define N800_TSC_RESET_GPIO		119	/* ? */
+#define N8X0_TMP105_GPIO		125
+
+/* Config */
+#define XLDR_LL_UART			1
+
+/* Addresses on the I2C bus */
+#define N8X0_TMP105_ADDR		0x48
+#define N8X0_MENELAUS_ADDR		0x72
+
+/* Chipselects on GPMC NOR interface */
+#define N8X0_ONENAND_CS			0
+#define N8X0_USB_ASYNC_CS		1
+#define N8X0_USB_SYNC_CS		4
+
+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 n8x0_nand_setup(struct n800_s *s)
+{
+    /* Either ec40xx or ec48xx are OK for the ID */
+    omap_gpmc_attach(s->cpu->gpmc, N8X0_ONENAND_CS, 0, onenand_base_update,
+                    onenand_base_unmap,
+                    onenand_init(0xec4800, 1,
+                            omap2_gpio_in_get(s->cpu->gpif,
+                                    N8X0_ONENAND_GPIO)[0]));
+}
+
+static void n800_i2c_setup(struct n800_s *s)
+{
+    qemu_irq tmp_irq = omap2_gpio_in_get(s->cpu->gpif, N8X0_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]),
+                    N8X0_MENELAUS_ADDR);
+
+    /* Attach a TMP105 PM chip (A0 wired to ground) */
+    i2c_set_slave_address(tmp105_init(s->i2c, tmp_irq), N8X0_TMP105_ADDR);
+}
+
+/* Touchscreen and keypad controller */
+#define RETU_KEYCODE	61	/* F3 */
+
+static void n800_key_event(void *opaque, int keycode)
+{
+    struct n800_s *s = (struct n800_s *) opaque;
+    int code = s->keymap[keycode & 0x7f];
+
+    if (code == -1) {
+        if ((keycode & 0x7f) == RETU_KEYCODE)
+            retu_key_event(s->retu, !(keycode & 0x80));
+        return;
+    }
+
+    tsc210x_key_event(s->ts, 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)
+{
+    uint8_t *fb_blank;
+
+    chip->write(chip->opaque, 0, 0x2a);		/* LCD Width register */
+    chip->write(chip->opaque, 1, 0x64);
+    chip->write(chip->opaque, 0, 0x2c);		/* LCD HNDP register */
+    chip->write(chip->opaque, 1, 0x1e);
+    chip->write(chip->opaque, 0, 0x2e);		/* LCD Height 0 register */
+    chip->write(chip->opaque, 1, 0xe0);
+    chip->write(chip->opaque, 0, 0x30);		/* LCD Height 1 register */
+    chip->write(chip->opaque, 1, 0x01);
+    chip->write(chip->opaque, 0, 0x32);		/* LCD VNDP register */
+    chip->write(chip->opaque, 1, 0x06);
+    chip->write(chip->opaque, 0, 0x68);		/* Display Mode register */
+    chip->write(chip->opaque, 1, 1);		/* Enable bit */
+
+    chip->write(chip->opaque, 0, 0x6c);	
+    chip->write(chip->opaque, 1, 0x00);		/* Input X Start Position */
+    chip->write(chip->opaque, 1, 0x00);		/* Input X Start Position */
+    chip->write(chip->opaque, 1, 0x00);		/* Input Y Start Position */
+    chip->write(chip->opaque, 1, 0x00);		/* Input Y Start Position */
+    chip->write(chip->opaque, 1, 0x1f);		/* Input X End Position */
+    chip->write(chip->opaque, 1, 0x03);		/* Input X End Position */
+    chip->write(chip->opaque, 1, 0xdf);		/* Input Y End Position */
+    chip->write(chip->opaque, 1, 0x01);		/* Input Y End Position */
+    chip->write(chip->opaque, 1, 0x00);		/* Output X Start Position */
+    chip->write(chip->opaque, 1, 0x00);		/* Output X Start Position */
+    chip->write(chip->opaque, 1, 0x00);		/* Output Y Start Position */
+    chip->write(chip->opaque, 1, 0x00);		/* Output Y Start Position */
+    chip->write(chip->opaque, 1, 0x1f);		/* Output X End Position */
+    chip->write(chip->opaque, 1, 0x03);		/* Output X End Position */
+    chip->write(chip->opaque, 1, 0xdf);		/* Output Y End Position */
+    chip->write(chip->opaque, 1, 0x01);		/* Output Y End Position */
+    chip->write(chip->opaque, 1, 0x01);		/* Input Data Format */
+    chip->write(chip->opaque, 1, 0x01);		/* Data Source Select */
+
+    fb_blank = memset(qemu_malloc(800 * 480 * 2), 0xff, 800 * 480 * 2);
+    /* Display Memory Data Port */
+    chip->block(chip->opaque, 1, fb_blank, 800 * 480 * 2, 800);
+    free(fb_blank);
+}
+
+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, N8X0_CBUS_DAT_GPIO)[0];
+    qemu_irq retu_irq = omap2_gpio_in_get(s->cpu->gpif, N8X0_RETU_GPIO)[0];
+    qemu_irq tahvo_irq = omap2_gpio_in_get(s->cpu->gpif, N8X0_TAHVO_GPIO)[0];
+
+    struct cbus_s *cbus = cbus_init(dat_out);
+
+    omap2_gpio_out_set(s->cpu->gpif, N8X0_CBUS_CLK_GPIO, cbus->clk);
+    omap2_gpio_out_set(s->cpu->gpif, N8X0_CBUS_DAT_GPIO, cbus->dat);
+    omap2_gpio_out_set(s->cpu->gpif, N8X0_CBUS_SEL_GPIO, cbus->sel);
+
+    cbus_attach(cbus, s->retu = retu_init(retu_irq, 1));
+    cbus_attach(cbus, s->tahvo = tahvo_init(tahvo_irq, 1));
+}
+
+/* This task is normally performed by the bootloader.  If we're loading
+ * a kernel directly, we need to set up GPMC mappings ourselves.  */
+static void n800_gpmc_init(struct n800_s *s)
+{
+    uint32_t config7 =
+            (0xf << 8) |	/* MASKADDRESS */
+            (1 << 6) |		/* CSVALID */
+            (4 << 0);		/* BASEADDRESS */
+
+    cpu_physical_memory_write(0x6800a078,		/* GPMC_CONFIG7_0 */
+                    (void *) &config7, sizeof(config7));
+}
+
+/* Setup sequence done by the bootloader */
+static void 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;
+    s->cpu->env->GE = 0x5;
+}
+
+#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 ++, N8X0_RETU_GPIO);		/* s16 retu_irq_gpio */
+    stw_raw(w ++, N8X0_TAHVO_GPIO);		/* s16 tahvo_irq_gpio */
+
+    stw_raw(w ++, OMAP_TAG_CBUS);		/* u16 tag */
+    stw_raw(w ++, 8);				/* u16 len */
+    stw_raw(w ++, N8X0_CBUS_CLK_GPIO);		/* s16 clk_gpio */
+    stw_raw(w ++, N8X0_CBUS_DAT_GPIO);		/* s16 dat_gpio */
+    stw_raw(w ++, N8X0_CBUS_SEL_GPIO);		/* s16 sel_gpio */
+    w ++;
+
+    stw_raw(w ++, OMAP_TAG_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 ++, N8X0_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;
+
+#if 0	/* N810 */
+    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-44");		/* 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.10-qemu");		/* char version[12] */
+    w += 6;
+#else
+    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;
+#endif
+
+    stw_raw(w ++, OMAP_TAG_LCD);		/* u16 tag */
+    stw_raw(w ++, 36);				/* u16 len */
+    strcpy((void *) w, "QEMU LCD panel");	/* char panel_name[16] */
+    w += 8;
+    strcpy((void *) w, "blizzard");		/* char ctrl_name[16] */
+    w += 8;
+    stw_raw(w ++, 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);
+    n8x0_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/omap2.c b/hw/omap2.c
index 188e092..67e5223 100644
--- a/hw/omap2.c
+++ b/hw/omap2.c
@@ -3496,7 +3496,7 @@
 {
     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;
+    ram_addr_t sram_base, q2_base;
     qemu_irq *cpu_irq;
     qemu_irq dma_irqs[4];
     omap_clk gpio_clks[4];
@@ -3520,7 +3520,7 @@
 
     /* Memory-mapped stuff */
     cpu_register_physical_memory(OMAP2_Q2_BASE, s->sdram_size,
-                    (q3_base = qemu_ram_alloc(s->sdram_size)) | IO_MEM_RAM);
+                    (q2_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);
 
diff --git a/hw/onenand.c b/hw/onenand.c
new file mode 100644
index 0000000..d63ecea
--- /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 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, 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/tmp105.c b/hw/tmp105.c
new file mode 100644
index 0000000..6d0505d
--- /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 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, 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/twl92230.c b/hw/twl92230.c
new file mode 100644
index 0000000..8c5ee03
--- /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 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, 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;
+}