Patchwork [11/14] ARM: s5pc210: added s5pc210 display controller device (FIMD)

login
register
mail settings
Submitter Evgeny Voevodin
Date Dec. 7, 2011, 9:47 a.m.
Message ID <1323251225-1072-12-git-send-email-e.voevodin@samsung.com>
Download mbox | patch
Permalink /patch/129931/
State New
Headers show

Comments

Evgeny Voevodin - Dec. 7, 2011, 9:47 a.m.
From: Mitsyanko Igor <i.mitsyanko@samsung.com>


Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
 Makefile.target   |    2 +-
 hw/s5pc210.c      |   11 +
 hw/s5pc210_fimd.c | 1698 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1710 insertions(+), 1 deletions(-)
 create mode 100644 hw/s5pc210_fimd.c

Patch

diff --git a/Makefile.target b/Makefile.target
index 805993b..84c4a05 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -345,7 +345,7 @@  obj-arm-y += arm_boot.o pl011.o pl031.o pl050.o pl080.o pl110.o pl181.o pl190.o
 obj-arm-y += versatile_pci.o
 obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
 obj-arm-y += s5pc210.o s5pc210_cmu.o s5pc210_uart.o s5pc210_gic.o \
-             s5pc210_combiner.o s5pc210_pwm.o s5pc210_mct.o
+             s5pc210_combiner.o s5pc210_pwm.o s5pc210_mct.o s5pc210_fimd.o
 obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
 obj-arm-y += pl061.o
 obj-arm-y += arm-semi.o
diff --git a/hw/s5pc210.c b/hw/s5pc210.c
index 8678b97..c7cba3e 100644
--- a/hw/s5pc210.c
+++ b/hw/s5pc210.c
@@ -97,6 +97,10 @@ 
 /* MCT */
 #define S5PC210_MCT_BASE_ADDR            0x10050000
 
+/* Display controllers (FIMD) */
+#define S5PC210_FIMD0_BASE_ADDR          0x11C00000
+#define S5PC210_FIMD1_BASE_ADDR          0x12000000
+
 #define S5PC210_BASE_BOOT_ADDR           S5PC210_DRAM0_BASE_ADDR
 
 /* Secondary CPU startup code is in IROM memory */
@@ -465,6 +469,13 @@  static void s5pc210_init(ram_addr_t ram_size,
         }
     }
 
+    /*** Display controller (FIMD) ***/
+    sysbus_create_varargs("s5pc210.fimd", S5PC210_FIMD0_BASE_ADDR,
+            irq_table[s5pc210_get_irq(11, 0)],
+            irq_table[s5pc210_get_irq(11, 1)],
+            irq_table[s5pc210_get_irq(11, 2)],
+            NULL);
+
     /*** Load kernel ***/
 
     s5pc210_binfo.ram_size = ram_size;
diff --git a/hw/s5pc210_fimd.c b/hw/s5pc210_fimd.c
new file mode 100644
index 0000000..b3b01f2
--- /dev/null
+++ b/hw/s5pc210_fimd.c
@@ -0,0 +1,1698 @@ 
+/*
+ * Samsung s5pc210 Display Controller (FIMD)
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ * Based on LCD controller for Samsung S5PC1xx-based board emulation
+ * by Kirill Batuzov <batuzovk@ispras.ru>
+ *
+ * Contributed by Mitsyanko Igor <i.mitsyanko@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "qemu-common.h"
+#include "cpu-all.h"
+#include "sysbus.h"
+#include "console.h"
+#include "pixel_ops.h"
+
+/* Debug messages configuration */
+#define S5P_FIMD_DEBUG              0
+#define S5P_FIMD_MODE_TRACE         0
+
+#if S5P_FIMD_DEBUG == 0
+    #define print_debug1(fmt, args...)   do { } while (0)
+    #define print_debug2(fmt, args...)   do { } while (0)
+    #define print_error(fmt, args...)  \
+    do {fprintf(stderr, "QEMU FIMD ERROR: "fmt, ## args); } while (0)
+#elif S5P_FIMD_DEBUG == 1
+    #define print_debug1(fmt, args...) \
+    do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+    #define print_debug2(fmt, args...)   do { } while (0)
+    #define print_error(fmt, args...)  \
+    do {fprintf(stderr, "QEMU FIMD ERROR: "fmt, ## args); } while (0)
+#else
+    #define print_debug1(fmt, args...) \
+    do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+    #define print_debug2(fmt, args...) \
+    do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+    #define print_error(fmt, args...)  \
+    do {fprintf(stderr, "QEMU FIMD ERROR: "fmt, ## args); } while (0)
+#endif
+
+
+#define NUM_OF_WINDOWS              5
+#define FIMD_REGS_SIZE              0x4114
+
+/* Video main control registers */
+#define FIMD_VIDCON0                0x0000
+#define FIMD_VIDCON1                0x0004
+#define FIMD_VIDCON2                0x0008
+#define FIMD_VIDCON3                0x000C
+#define FIMD_VIDCON0_ENVID_F        (1 << 0)
+#define FIMD_VIDCON0_ENVID          (1 << 1)
+#define FIMD_VIDCON0_ENVID_MASK     ((1 << 0) | (1 << 1))
+#define FIMD_VIDCON1_ROMASK         0x07FFE000
+
+/* Video time control registers */
+#define FIMD_VIDTCON2_SIZE_MASK     0x07FF
+#define FIMD_VIDTCON2_HOR_SHIFT     0
+#define FIMD_VIDTCON2_VER_SHIFT     11
+
+/* Window control registers */
+#define FIMD_WINCON0                0x0020
+#define FIMD_WINCON_ROMASK          0x82200000
+#define FIMD_WINCON_ENWIN           (1 << 0)
+#define FIMD_WINCON_BLD_PIX         (1 << 6)
+#define FIMD_WINCON_ALPHA_SEL       (1 << 1)
+#define FIMD_WINCON_ALSEL_SHIFT     1
+#define FIMD_WINCON_SWAP            0x078000
+#define FIMD_WINCON_SWAP_SHIFT      15
+#define FIMD_WINCON_SWAP_WORD       0x1
+#define FIMD_WINCON_SWAP_HWORD      0x2
+#define FIMD_WINCON_SWAP_BYTE       0x4
+#define FIMD_WINCON_SWAP_BITS       0x8
+#define FIMD_WINCON_BPPMODE_SHIFT   2
+#define FIMD_WINCON_BPPMODE         0x0F
+#define FIMD_WINCON_BUFSTATUS_L     (1 << 21)
+#define FIMD_WINCON_BUFSTATUS_H     (1 << 31)
+#define FIMD_WINCON_BUFSTATUS       ((1 << 21) | (1 << 31))
+#define FIMD_WINCON_BUF0_STAT       ((0 << 21) | (0 << 31))
+#define FIMD_WINCON_BUF1_STAT       ((1 << 21) | (0 << 31))
+#define FIMD_WINCON_BUF2_STAT       ((0 << 21) | (1 << 31))
+#define FIMD_WINCON_BUFSELECT       ((1 << 20) | (1 << 30))
+#define FIMD_WINCON_BUF0_SEL        ((0 << 20) | (0 << 30))
+#define FIMD_WINCON_BUF1_SEL        ((1 << 20) | (0 << 30))
+#define FIMD_WINCON_BUF2_SEL        ((0 << 20) | (1 << 30))
+#define FIMD_WINCON_BUFMODE         (1 << 14)
+
+/* Window position control registers */
+#define FIMD_VIDOSD_COORD_MASK      0x07FF
+#define FIMD_VIDOSD_HOR_SHIFT       11
+#define FIMD_VIDOSD_VER_SHIFT       0
+#define FIMD_VIDOSD_ALPHA_AEN0      0xFFF000
+#define FIMD_VIDOSD_AEN0_SHIFT      12
+#define FIMD_VIDOSD_ALPHA_AEN1      0x000FFF
+
+/* Frame buffer address registers */
+#define FIMD_VIDWADD2_PAGEWIDTH     0x1FFF
+#define FIMD_VIDWADD2_OFFSIZE       0x1FFF
+#define FIMD_VIDWADD2_OFFSIZE_SHIFT 13
+
+/* Window color key registers */
+#define FIMD_WKEYCON0_COMPKEY       0x00FFFFFF
+#define FIMD_WKEYCON0_CTL_SHIFT     24
+#define FIMD_WKEYCON0_CTL_DIRCON    0x1
+#define FIMD_WKEYCON0_CTL_KEYEN     0x2
+#define FIMD_WKEYCON0_CTL_KEYBLEN   0x4
+
+/* Window alpha control registers */
+#define FIMD_VIDALPHA_ALPHA_MASK    0x000F0F0F
+
+/* Window color map registers */
+#define FIMD_WINMAP_EN              (1 << 24)
+#define FIMD_WINMAP_COLOR_MASK      0x00FFFFFF
+#define FIMD_WINMAP_NOCOLOR         (~FIMD_WINMAP_COLOR_MASK)
+
+/* Window palette control registers */
+#define FIMD_WPAL_W0PAL_L           0x07
+#define FIMD_WPAL_W0PAL_L_SH     0
+#define FIMD_WPAL_W1PAL_L           0x07
+#define FIMD_WPAL_W1PAL_L_SH     3
+#define FIMD_WPAL_W2PAL_L           0x01
+#define FIMD_WPAL_W2PAL_L_SH     6
+#define FIMD_WPAL_W2PAL_H           0x06
+#define FIMD_WPAL_W2PAL_H_SH     8
+#define FIMD_WPAL_W3PAL_L           0x01
+#define FIMD_WPAL_W3PAL_L_SH     7
+#define FIMD_WPAL_W3PAL_H           0x06
+#define FIMD_WPAL_W3PAL_H_SH     12
+#define FIMD_WPAL_W4PAL_L           0x01
+#define FIMD_WPAL_W4PAL_L_SH     8
+#define FIMD_WPAL_W4PAL_H           0x06
+#define FIMD_WPAL_W4PAL_H_SH     16
+
+/* Trigger control registers */
+#define FIMD_TRIGCON                0x01A4
+#define FIMD_TRIGCON_ROMASK         0x00000004
+
+/* Video interrupt control registers */
+#define FIMD_VIDINT_INTFIFOPEND     (1 << 0)
+#define FIMD_VIDINT_INTFRMPEND      (1 << 1)
+#define FIMD_VIDINT_INTI80PEND      (1 << 2)
+#define FIMD_VIDINT_INTEN           (1 << 0)
+#define FIMD_VIDINT_INTFIFOEN       (1 << 1)
+#define FIMD_VIDINT_INTFRMEN        (1 << 12)
+#define FIMD_VIDINT_I80IFDONE       (1 << 17)
+
+/* Window blend equation control registers */
+#define FIMD_BLENDEQ_COEF_MASK      0xF
+#define FIMD_BLENDEQ_COEF_Q         18
+#define FIMD_BLENDEQ_COEF_P         12
+#define FIMD_BLENDEQ_COEF_B         6
+#define FIMD_BLENDEQ_COEF_A         0
+
+typedef struct {
+    uint8_t r, g, b;
+    uint32_t a;
+} rgba;
+#define RGBA_SIZE  7
+
+typedef struct DrawConfig DrawConfig;
+
+typedef void pixel_to_rgb_func(uint32_t pixel, rgba *p);
+typedef void draw_line_func(struct DrawConfig *cfg, uint8_t *src,
+                            uint8_t *dst, uint8_t *ifb);
+typedef uint32_t coef_func(const struct DrawConfig *cfg, rgba pa, rgba pb);
+
+typedef struct {
+    uint32_t wincon;        /* Window control register */
+    uint32_t vidosd[4];     /* Window position control registers A-D */
+    uint32_t buf_start[3];  /* Start address for video frame buffer */
+    uint32_t buf_end[3];    /* End address for video frame buffer */
+    uint32_t buf_size;      /* Virtual screen width */
+    uint32_t keycon[2];     /* Window color key registers */
+    uint32_t keyalpha;      /* Color key alpha control register */
+    uint32_t winmap;        /* Window color map register */
+    uint32_t vidw_alpha[2]; /* Window alpha control registers */
+    uint32_t blendeq;       /* Window blending equation control register */
+    uint32_t rtqoscon;      /* Window RTQOS Control Registers */
+    uint32_t palette[256];  /* Pallete RAM */
+    uint32_t shadow_buf_start;      /* Start address of shadow frame buffer */
+    uint32_t shadow_buf_end;        /* End address of shadow frame buffer */
+    uint32_t shadow_buf_size;       /* Virtual shadow screen width */
+} S5pc210fimdWindow;
+
+typedef struct {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    DisplayState *console;
+    qemu_irq irq[3];
+
+    uint32_t vidcon[4];     /* Video main control registers 0-3 */
+    uint32_t vidtcon[4];    /* Video time control registers 0-3 */
+    uint32_t shadowcon;     /* Window shadow control register */
+    uint32_t winchmap;      /* Channel maping control register */
+    uint32_t vidintcon[2];  /* Video interrupt control registers */
+    uint32_t dithmode;      /* Dithering control register */
+    uint32_t wpalcon[2];    /* Window pallete control registers */
+    uint32_t trigcon;       /* Trigger control register */
+    uint32_t i80ifcon[4];   /* I80 interface control registers */
+    uint32_t colorgaincon;  /* Color gain control register */
+    uint32_t ldi_cmdcon[2]; /* LCD I80 interface command control */
+    uint32_t sifccon[3];    /* I80 System Interface Manual Command Control */
+    uint32_t huecoef_cr[4]; /* Hue control registers */
+    uint32_t huecoef_cb[4]; /* Hue control registers */
+    uint32_t hueoffset;     /* Hue offset control register */
+    uint32_t blendcon;      /* Blending equation control register */
+    uint32_t dualrgb;       /* Undocumented register */
+    uint32_t i80ifcmd[12];  /* LCD I80 Interface Command */
+
+    S5pc210fimdWindow window[5];    /* Window-specific registers */
+    uint8_t *ifb;                   /* Internal frame buffer */
+    bool invalidate;                /* Image needs to be redrawn */
+    bool enabled;                   /* Display controller is enabled */
+} S5pc210fimdState;
+
+struct DrawConfig {
+    pixel_to_rgb_func *pixel_to_rgb;
+    draw_line_func *draw_line;
+    int (*put_pixel)(const rgba p, uint8_t *pixel);
+    int (*get_pixel)(const uint8_t *src, rgba *p);
+    void (*blend)(struct DrawConfig *cfg, rgba p_old, rgba p_new, rgba *p);
+    coef_func *coef_p, *coef_q, *coef_a, *coef_b;
+    uint8_t is_palletized;
+    uint32_t bg_alpha[2], fg_alpha[2];
+    uint32_t color_key, color_mask, color_ctl;
+    uint8_t fg_alpha_pix, bg_alpha_pix;
+    int width;
+    int bpp;
+    uint32_t *palette;
+    uint8_t swap;
+    uint8_t fg_pixel_blending, bg_pixel_blending;
+    uint8_t fg_alpha_sel, bg_alpha_sel;
+    uint32_t map_color;
+};
+
+static inline int s5pc210_buffer_status(S5pc210fimdWindow *w)
+{
+    switch (w->wincon & FIMD_WINCON_BUFSTATUS) {
+    case FIMD_WINCON_BUF0_STAT: default:
+        return 0;
+    case FIMD_WINCON_BUF1_STAT:
+        return 1;
+    case FIMD_WINCON_BUF2_STAT:
+        return 2;
+    }
+}
+
+/* Perform byte/halfword/word swap of data according to config */
+static inline uint64_t swap_data(const DrawConfig *cfg, uint64_t x)
+{
+    int i;
+    uint64_t res;
+
+    if (cfg->swap & FIMD_WINCON_SWAP_BITS) {
+        res = 0;
+        for (i = 0; i < 64; i++) {
+            if (x & (1ULL << (64 - i))) {
+                res |= (1ULL << i);
+            }
+        }
+        x = res;
+    }
+    if (cfg->swap & FIMD_WINCON_SWAP_BYTE) {
+        x = ((x & 0x00000000000000FFULL) << 56) |
+            ((x & 0x000000000000FF00ULL) << 40) |
+            ((x & 0x0000000000FF0000ULL) << 24) |
+            ((x & 0x00000000FF000000ULL) <<  8) |
+            ((x & 0x000000FF00000000ULL) >>  8) |
+            ((x & 0x0000FF0000000000ULL) >> 24) |
+            ((x & 0x00FF000000000000ULL) >> 40) |
+            ((x & 0xFF00000000000000ULL) >> 56);
+    }
+    if (cfg->swap & FIMD_WINCON_SWAP_HWORD) {
+        x = ((x & 0x000000000000FFFFULL) << 48) |
+            ((x & 0x00000000FFFF0000ULL) << 16) |
+            ((x & 0x0000FFFF00000000ULL) >> 16) |
+            ((x & 0xFFFF000000000000ULL) >> 48);
+    }
+    if (cfg->swap & FIMD_WINCON_SWAP_WORD) {
+        x = ((x & 0x00000000FFFFFFFFULL) << 32) |
+            ((x & 0xFFFFFFFF00000000ULL) >> 32);
+    }
+    return x;
+}
+
+/* Palette/pixel to RGB conversion */
+
+#define DEF_PIXEL_TO_RGB(N, R, G, B, A) \
+static void N(uint32_t pixel, rgba *p) \
+{ \
+    p->b = (pixel & ((1 << (B)) - 1)) << (8 - (B)); \
+    pixel >>= (B); \
+    p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)); \
+    pixel >>= (G); \
+    p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)); \
+    pixel >>= (R); \
+    if (1 == (A)) { \
+        p->a = pixel & 1; \
+    } else if (8 == (A)) { \
+        p->a = pixel & 0xFF; \
+        p->a = (p->a << 16) | (p->a << 8) | p->a; \
+    } else { \
+        p->a = (pixel & ((1 << (A)) - 1)) << (8 - (A)); \
+    } \
+}
+
+DEF_PIXEL_TO_RGB(pixel_a232_to_rgb, 2, 3, 2, 1)
+DEF_PIXEL_TO_RGB(pixel_a444_to_rgb, 4, 4, 4, 1)
+DEF_PIXEL_TO_RGB(pixel_4444_to_rgb, 4, 4, 4, 4)
+DEF_PIXEL_TO_RGB(pixel_565_to_rgb,  5, 6, 5, 0)
+DEF_PIXEL_TO_RGB(pixel_a555_to_rgb, 5, 5, 5, 1)
+DEF_PIXEL_TO_RGB(pixel_555_to_rgb,  5, 5, 5, 0)
+DEF_PIXEL_TO_RGB(pixel_666_to_rgb,  6, 6, 6, 0)
+DEF_PIXEL_TO_RGB(pixel_a666_to_rgb, 6, 6, 6, 1)
+DEF_PIXEL_TO_RGB(pixel_a665_to_rgb, 6, 6, 5, 1)
+DEF_PIXEL_TO_RGB(pixel_888_to_rgb,  8, 8, 8, 0)
+DEF_PIXEL_TO_RGB(pixel_a888_to_rgb, 8, 8, 8, 1)
+DEF_PIXEL_TO_RGB(pixel_a887_to_rgb, 8, 8, 7, 1)
+DEF_PIXEL_TO_RGB(pixel_8888_to_rgb, 8, 8, 8, 8)
+
+/* Special case for (5+1,5+1,5+1) mode */
+static void pixel_1555_to_rgb(uint32_t pixel, rgba *p)
+{
+    uint8_t u = (pixel >> 15) & 1;
+    p->b = (((pixel & 0x1F) << 1) | u) << 2;
+    pixel >>= 5;
+    p->g = (((pixel & 0x3F) << 1) | u) << 2;
+    pixel >>= 6;
+    p->r = (((pixel & 0x1F) << 1) | u) << 2;
+}
+
+/* Draw line with pallete index in frame buffer data */
+#define DEF_DRAW_LINE_PALLETE(N) \
+static void glue(draw_line_pallete_, N)(DrawConfig *cfg, uint8_t *src, \
+                               uint8_t *dst, uint8_t *ifb) \
+{ \
+    int width = cfg->width; \
+    uint64_t data; \
+    rgba p, p_old; \
+    int i; \
+    do { \
+        data = ldq_raw((void *)src); \
+        src += 8; \
+        data = swap_data(cfg, data); \
+        for (i = (64 / (N) - 1); i >= 0; i--) { \
+            cfg->pixel_to_rgb(cfg->palette[(data >> ((N) * i)) & \
+                                   ((1ULL << (N)) - 1)], &p); \
+            if (cfg->blend) { \
+                ifb += cfg->get_pixel(ifb, &p_old); \
+                cfg->blend(cfg, p_old, p, &p); \
+            } \
+            dst += cfg->put_pixel(p, dst); \
+        } \
+        width -= (64 / (N)); \
+    } while (width > 0); \
+}
+
+/* Draw line with direct color value in frame buffer data */
+#define DEF_DRAW_LINE_NOPALLETE(N) \
+static void glue(draw_line_, N)(DrawConfig *cfg, uint8_t *src, \
+                               uint8_t *dst, uint8_t *ifb) \
+{ \
+    int width = cfg->width; \
+    uint64_t data; \
+    rgba p, p_old; \
+    int i; \
+    do { \
+        data = ldq_raw((void *)src); \
+        src += 8; \
+        data = swap_data(cfg, data); \
+        for (i = (64 / (N) - 1); i >= 0; i--) { \
+            cfg->pixel_to_rgb((data >> ((N) * i)) & ((1ULL << (N)) - 1), &p); \
+            if (cfg->blend) { \
+                ifb += cfg->get_pixel(ifb, &p_old); \
+                cfg->blend(cfg, p_old, p, &p); \
+            } \
+            dst += cfg->put_pixel(p, dst); \
+        } \
+        width -= (64 / (N)); \
+    } while (width > 0); \
+}
+
+DEF_DRAW_LINE_PALLETE(1)
+DEF_DRAW_LINE_PALLETE(2)
+DEF_DRAW_LINE_PALLETE(4)
+DEF_DRAW_LINE_PALLETE(8)
+DEF_DRAW_LINE_NOPALLETE(8)  /* 8bpp mode has pallete and non-pallete versions */
+DEF_DRAW_LINE_NOPALLETE(16)
+DEF_DRAW_LINE_NOPALLETE(32)
+
+/* Special draw line routine for window color map case */
+static void draw_line_mapcolor(DrawConfig *cfg, uint8_t *src,
+                               uint8_t *dst, uint8_t *ifb)
+{
+    rgba p, p_old;
+    int width = cfg->width;
+    uint32_t map_color = cfg->map_color;
+
+    do {
+        pixel_888_to_rgb(map_color, &p);
+        if (cfg->blend) {
+            ifb += cfg->get_pixel(ifb, &p_old);
+            cfg->blend(cfg, p_old, p, &p);
+        }
+        dst += cfg->put_pixel(p, dst);
+    } while (--width);
+}
+
+/* Routine to copy line from internal frame buffer to QEMU display */
+static void draw_line_copy(DrawConfig *cfg, uint8_t *src, uint8_t *dst)
+{
+    rgba p;
+    int width = cfg->width;
+
+    do {
+        src += cfg->get_pixel(src, &p);
+        dst += cfg->put_pixel(p, dst);
+    } while (--width);
+}
+
+/* Parse BPPMODE_F bits and setup known DRAW_CONFIG fields accordingly.
+   BPPMODE_F = WINCON1[5:2] */
+static void s5pc210_parse_win_bppmode(S5pc210fimdWindow *w, DrawConfig *cfg)
+{
+    switch ((w->wincon >> FIMD_WINCON_BPPMODE_SHIFT) & FIMD_WINCON_BPPMODE) {
+    case 0:
+        cfg->draw_line = draw_line_pallete_1;
+        cfg->is_palletized = 1;
+        cfg->bpp = 1;
+        break;
+    case 1:
+        cfg->draw_line = draw_line_pallete_2;
+        cfg->is_palletized = 1;
+        cfg->bpp = 2;
+        break;
+    case 2:
+        cfg->draw_line = draw_line_pallete_4;
+        cfg->is_palletized = 1;
+        cfg->bpp = 4;
+        break;
+    case 3:
+        cfg->draw_line = draw_line_pallete_8;
+        cfg->is_palletized = 1;
+        cfg->bpp = 8;
+        break;
+    case 4:
+        cfg->draw_line = draw_line_8;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_a232_to_rgb;
+        cfg->bpp = 8;
+        break;
+    case 5:
+        cfg->draw_line = draw_line_16;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_565_to_rgb;
+        cfg->bpp = 16;
+        break;
+    case 6:
+        cfg->draw_line = draw_line_16;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_a555_to_rgb;
+        cfg->bpp = 16;
+        break;
+    case 7:
+        cfg->draw_line = draw_line_16;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_1555_to_rgb;
+        cfg->bpp = 16;
+        break;
+    case 8:
+        cfg->draw_line = draw_line_32;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_666_to_rgb;
+        cfg->bpp = 32;
+        break;
+    case 9:
+        cfg->draw_line = draw_line_32;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_a665_to_rgb;
+        cfg->bpp = 32;
+        break;
+    case 10:
+        cfg->draw_line = draw_line_32;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_a666_to_rgb;
+        cfg->bpp = 32;
+        break;
+    case 11:
+        cfg->draw_line = draw_line_32;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_888_to_rgb;
+        cfg->bpp = 32;
+        break;
+    case 12:
+        cfg->draw_line = draw_line_32;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_a887_to_rgb;
+        cfg->bpp = 32;
+        break;
+    case 13:
+        cfg->draw_line = draw_line_32;
+        cfg->is_palletized = 0;
+        if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon &
+                FIMD_WINCON_ALPHA_SEL)) {
+            cfg->pixel_to_rgb = pixel_8888_to_rgb;
+            cfg->fg_alpha_pix = 1;
+        } else {
+            cfg->pixel_to_rgb = pixel_a888_to_rgb;
+        }
+        cfg->bpp = 32;
+        break;
+    case 14:
+        cfg->draw_line = draw_line_16;
+        cfg->is_palletized = 0;
+        if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon &
+                FIMD_WINCON_ALPHA_SEL)) {
+            cfg->pixel_to_rgb = pixel_4444_to_rgb;
+            cfg->fg_alpha_pix = 1;
+        } else {
+            cfg->pixel_to_rgb = pixel_a444_to_rgb;
+        }
+        cfg->bpp = 16;
+        break;
+    case 15:
+        cfg->draw_line = draw_line_16;
+        cfg->is_palletized = 0;
+        cfg->pixel_to_rgb = pixel_555_to_rgb;
+        cfg->bpp = 16;
+        break;
+    }
+}
+
+#if S5P_FIMD_MODE_TRACE > 0
+static const char *s5pc210_fimd_get_bppmode(int mode_code)
+{
+    switch (mode_code) {
+    case 0:
+        return "1 bpp";
+    case 1:
+        return "2 bpp";
+    case 2:
+        return "4 bpp";
+    case 3:
+        return "8 bpp (palletized)";
+    case 4:
+        return "8 bpp (non-palletized, A: 1-R:2-G:3-B:2)";
+    case 5:
+        return "16 bpp (non-palletized, R:5-G:6-B:5)";
+    case 6:
+        return "16 bpp (non-palletized, A:1-R:5-G:5-B:5)";
+    case 7:
+        return "16 bpp (non-palletized, I :1-R:5-G:5-B:5)";
+    case 8:
+        return "Unpacked 18 bpp (non-palletized, R:6-G:6-B:6)";
+    case 9:
+        return "Unpacked 18bpp (non-palletized,A:1-R:6-G:6-B:5)";
+    case 10:
+        return "Unpacked 19bpp (non-palletized,A:1-R:6-G:6-B:6)";
+    case 11:
+        return "Unpacked 24 bpp (non-palletized R:8-G:8-B:8)";
+    case 12:
+        return "Unpacked 24 bpp (non-palletized A:1-R:8-G:8-B:7)";
+    case 13:
+        return "Unpacked 25 bpp (non-palletized A:1-R:8-G:8-B:8)";
+    case 14:
+        return "Unpacked 13 bpp (non-palletized A:1-R:4-G:4-B:4)";
+    case 15:
+        return "Unpacked 15 bpp (non-palletized R:5-G:5-B:5)";
+    default:
+        return "Non-existing bpp mode";
+    }
+}
+#endif
+
+static inline void s5pc210_fimd_trace_bppmode(S5pc210fimdWindow *w,
+                int win_num, uint32_t val)
+{
+#if S5P_FIMD_MODE_TRACE > 0
+    if (((w->wincon >> FIMD_WINCON_BPPMODE_SHIFT) & FIMD_WINCON_BPPMODE) ==
+            ((val >> FIMD_WINCON_BPPMODE_SHIFT) & FIMD_WINCON_BPPMODE)) {
+        return;
+    }
+    printf("QEMU FIMD: Window %d BPP mode changed from %s to %s\n", win_num,
+        s5pc210_fimd_get_bppmode((w->wincon >> FIMD_WINCON_BPPMODE_SHIFT) &
+                FIMD_WINCON_BPPMODE),
+        s5pc210_fimd_get_bppmode((val >> FIMD_WINCON_BPPMODE_SHIFT) &
+                FIMD_WINCON_BPPMODE));
+#endif
+}
+
+static inline void s5pc210_fimd_trace_reset(void)
+{
+#if S5P_FIMD_MODE_TRACE > 0
+    fprintf(stderr, "QEMU FIMD: Display controller reset\n");
+#endif
+}
+
+static inline void s5pc210_fimd_enable(S5pc210fimdState *s, bool enabled)
+{
+    s->enabled = enabled ? true : false;
+#if S5P_FIMD_MODE_TRACE > 0
+    fprintf(stderr, "QEMU FIMD: display controller %s\n",
+            (enabled ? "enabled" : "disabled"));
+#endif
+}
+
+/* Returns WxPAL for given window number WINDOW */
+static uint32_t s5pc210_wxpal(S5pc210fimdState *s, int window)
+{
+    switch (window) {
+    case 0:
+        return (s->wpalcon[1] >> FIMD_WPAL_W0PAL_L_SH) & FIMD_WPAL_W0PAL_L;
+    case 1:
+        return (s->wpalcon[1] >> FIMD_WPAL_W1PAL_L_SH) & FIMD_WPAL_W1PAL_L;
+    case 2:
+        return ((s->wpalcon[0] >> FIMD_WPAL_W2PAL_H_SH) & FIMD_WPAL_W2PAL_H) |
+            ((s->wpalcon[1] >> FIMD_WPAL_W2PAL_L_SH) & FIMD_WPAL_W2PAL_L);
+    case 3:
+        return ((s->wpalcon[0] >> FIMD_WPAL_W3PAL_H_SH) & FIMD_WPAL_W3PAL_H) |
+            ((s->wpalcon[1] >> FIMD_WPAL_W3PAL_L_SH) & FIMD_WPAL_W3PAL_L);
+    case 4:
+        return ((s->wpalcon[0] >> FIMD_WPAL_W4PAL_H_SH) & FIMD_WPAL_W4PAL_H) |
+            ((s->wpalcon[1] >> FIMD_WPAL_W4PAL_L_SH) & FIMD_WPAL_W4PAL_L);
+    }
+    hw_error("s5pc210.fimd: incorrect window number %d\n", window);
+    return 0;
+}
+
+pixel_to_rgb_func *wxpal_to_rgb[8] = {
+    [0] = pixel_565_to_rgb,
+    [1] = pixel_a555_to_rgb,
+    [2] = pixel_666_to_rgb,
+    [3] = pixel_a665_to_rgb,
+    [4] = pixel_a666_to_rgb,
+    [5] = pixel_888_to_rgb,
+    [6] = pixel_a888_to_rgb,
+    [7] = pixel_8888_to_rgb
+};
+
+/* Put/get pixel to/from internal LCD Controller framebuffer */
+
+static int put_rgba(const rgba p, uint8_t *d)
+{
+    *(uint8_t *)d++ = p.r;
+    *(uint8_t *)d++ = p.g;
+    *(uint8_t *)d++ = p.b;
+    *(uint32_t *)d = p.a;
+    return RGBA_SIZE;
+}
+
+static int get_rgba(const uint8_t *s, rgba *p)
+{
+    p->r = *(uint8_t *)s++;
+    p->g = *(uint8_t *)s++;
+    p->b = *(uint8_t *)s++;
+    p->a = *(uint32_t *)s;
+    return RGBA_SIZE;
+}
+
+/* Write RGB to QEMU's GraphicConsole framebuffer */
+
+static int put_pixel8(const rgba p, uint8_t *d)
+{
+    uint32_t pixel = rgb_to_pixel8(p.r, p.g, p.b);
+    *(uint8_t *)d = pixel;
+    return 1;
+}
+
+static int put_pixel15(const rgba p, uint8_t *d)
+{
+    uint32_t pixel = rgb_to_pixel15(p.r, p.g, p.b);
+    *(uint16_t *)d = pixel;
+    return 2;
+}
+
+static int put_pixel16(const rgba p, uint8_t *d)
+{
+    uint32_t pixel = rgb_to_pixel16(p.r, p.g, p.b);
+    *(uint16_t *)d = pixel;
+    return 2;
+}
+
+static int put_pixel24(const rgba p, uint8_t *d)
+{
+    uint32_t pixel = rgb_to_pixel24(p.r, p.g, p.b);
+    *(uint8_t *)d++ = (pixel >>  0) & 0xFF;
+    *(uint8_t *)d++ = (pixel >>  8) & 0xFF;
+    *(uint8_t *)d++ = (pixel >> 16) & 0xFF;
+    return 3;
+}
+
+static int put_pixel32(const rgba p, uint8_t *d)
+{
+    uint32_t pixel = rgb_to_pixel24(p.r, p.g, p.b);
+    *(uint32_t *)d = pixel;
+    return 4;
+}
+
+static inline uint32_t unpack_by_4(uint32_t x)
+{
+    return ((x & 0xF00) << 12) | ((x & 0xF0) << 8) | ((x & 0xF) << 4);
+}
+
+
+/* Coefficient extraction functions */
+
+static uint32_t coef_zero(const DrawConfig *cfg,
+                          rgba pa, rgba pb)
+{
+    return 0;
+}
+
+static uint32_t coef_one(const DrawConfig *cfg,
+                         rgba pa, rgba pb)
+{
+    return 0xFFFFFF;
+}
+
+static uint32_t coef_alphaa(const DrawConfig *cfg,
+                            rgba pa, rgba pb)
+{
+    if (!cfg->fg_pixel_blending) {
+        pa.a = cfg->fg_alpha_sel;
+    }
+    if (cfg->fg_alpha_pix) {
+        return pa.a;
+    } else {
+        return cfg->fg_alpha[pa.a];
+    }
+}
+
+static uint32_t coef_one_minus_alphaa(const DrawConfig *cfg,
+                                      rgba pa, rgba pb)
+{
+    if (!cfg->fg_pixel_blending) {
+        pa.a = cfg->fg_alpha_sel;
+    }
+    if (cfg->fg_alpha_pix) {
+        return 0xFFFFFF - pa.a;
+    } else {
+        return 0xFFFFFF - cfg->fg_alpha[pa.a];
+    }
+}
+
+static uint32_t coef_alphab(const DrawConfig *cfg,
+                            rgba pa, rgba pb)
+{
+    if (!cfg->bg_pixel_blending) {
+        pb.a = cfg->bg_alpha_sel;
+    }
+    if (cfg->bg_alpha_pix) {
+        return pb.a;
+    } else {
+        return cfg->bg_alpha[pb.a];
+    }
+}
+
+static uint32_t coef_one_minus_alphab(const DrawConfig *cfg,
+                                      rgba pa, rgba pb)
+{
+    if (!cfg->bg_pixel_blending) {
+        pb.a = cfg->bg_alpha_sel;
+    }
+    if (cfg->bg_alpha_pix) {
+        return 0xFFFFFF - pb.a;
+    } else {
+        return 0xFFFFFF - cfg->bg_alpha[pb.a];
+    }
+}
+
+static uint32_t coef_a(const DrawConfig *cfg,
+                       rgba pa, rgba pb)
+{
+    return (pa.r << 16) | (pa.g << 8) | pa.b;
+}
+
+static uint32_t coef_one_minus_a(const DrawConfig *cfg,
+                                 rgba pa, rgba pb)
+{
+    return 0xFFFFFF - ((pa.r << 16) | (pa.g << 8) | pa.b);
+}
+
+static uint32_t coef_b(const DrawConfig *cfg,
+                       rgba pa, rgba pb)
+{
+    return (pb.r << 16) | (pb.g << 8) | pb.b;
+}
+
+static uint32_t coef_one_minus_b(const DrawConfig *cfg,
+                                 rgba pa, rgba pb)
+{
+    return 0xFFFFFF - ((pb.r << 16) | (pb.g << 8) | pb.b);
+}
+
+
+static coef_func *coef_decode(uint32_t x)
+{
+    switch (x) {
+    case 0:
+        return coef_zero;
+    case 1:
+        return coef_one;
+    case 2:
+        return coef_alphaa;
+    case 3:
+        return coef_one_minus_alphaa;
+    case 4:
+        return coef_alphab;
+    case 5:
+        return coef_one_minus_alphab;
+    case 10:
+        return coef_a;
+    case 11:
+        return coef_one_minus_a;
+    case 12:
+        return coef_b;
+    case 13:
+        return coef_one_minus_b;
+    default:
+        hw_error("s5pc210.fimd: blend equation coef illegal value\n");
+        return 0;
+    }
+}
+
+static void blend_alpha(const DrawConfig *cfg, rgba p_bg, rgba p_fg, rgba *res)
+{
+    uint32_t pl, ql, al, bl;
+    uint32_t p, q, a, b, fg, bg, fga, bga;
+
+    pl = cfg->coef_p(cfg, p_fg, p_bg);
+    ql = cfg->coef_q(cfg, p_fg, p_bg);
+    al = cfg->coef_a(cfg, p_fg, p_bg);
+    bl = cfg->coef_b(cfg, p_fg, p_bg);
+    res->a = 0;
+    /* B */
+    p = pl & 0xFF;
+    pl >>= 8;
+    q = ql & 0xFF;
+    ql >>= 8;
+    a = al & 0xFF;
+    al >>= 8;
+    b = bl & 0xFF;
+    bl >>= 8;
+    fg = p_fg.b;
+    bg = p_bg.b;
+    if (cfg->fg_pixel_blending) {
+        if (cfg->fg_alpha_pix) {
+            fga = p_fg.a & 0xFF;
+        } else {
+            fga = cfg->fg_alpha[p_fg.a] & 0xFF;
+        }
+    } else {
+        fga = cfg->fg_alpha[cfg->fg_alpha_sel] & 0xFF;
+    }
+    if (cfg->bg_pixel_blending) {
+        if (cfg->bg_alpha_pix) {
+            bga = p_bg.a & 0xFF;
+        } else {
+            bga = cfg->bg_alpha[p_bg.a] & 0xFF;
+        }
+    } else {
+        bga = cfg->bg_alpha[cfg->bg_alpha_sel] & 0xFF;
+    }
+    bg = (bg * b + fg * a) / 0xFF;
+    if (bg > 0xFF) {
+        res->b = 0xFF;
+    } else {
+        res->b = bg;
+    }
+    bga = (bga * p + fga * q) / 0xFF;
+    if (bga > 0xFF) {
+        res->a |= 0xFF;
+    } else {
+        res->a |= bga;
+    }
+    /* G */
+    p = pl & 0xFF;
+    pl >>= 8;
+    q = ql & 0xFF;
+    ql >>= 8;
+    a = al & 0xFF;
+    al >>= 8;
+    b = bl & 0xFF;
+    bl >>= 8;
+    fg = p_fg.g;
+    bg = p_bg.g;
+    if (cfg->fg_pixel_blending) {
+        if (cfg->fg_alpha_pix) {
+            fga = (p_fg.a >> 8) & 0xFF;
+        } else {
+            fga = (cfg->fg_alpha[p_fg.a] >> 8) & 0xFF;
+        }
+    } else {
+        fga = (cfg->fg_alpha[cfg->fg_alpha_sel] >> 8) & 0xFF;
+    }
+    if (cfg->bg_pixel_blending) {
+        if (cfg->bg_alpha_pix) {
+            bga = (p_bg.a >> 8) & 0xFF;
+        } else {
+            bga = (cfg->bg_alpha[p_bg.a] >> 8) & 0xFF;
+        }
+    } else {
+        bga = (cfg->bg_alpha[cfg->bg_alpha_sel] >> 8) & 0xFF;
+    }
+    bg = (bg * b + fg * a) / 0xFF;
+    if (bg > 0xFF) {
+        res->g = 0xFF;
+    } else {
+        res->g = bg;
+    }
+    bga = (bga * p + fga * q) / 0xFF;
+    if (bga > 0xFF) {
+        res->a |= 0xFF << 8;
+    } else {
+        res->a |= bga << 8;
+    }
+    /* R */
+    p = pl & 0xFF;
+    pl >>= 8;
+    q = ql & 0xFF;
+    ql >>= 8;
+    a = al & 0xFF;
+    al >>= 8;
+    b = bl & 0xFF;
+    bl >>= 8;
+    fg = p_fg.r;
+    bg = p_bg.r;
+    if (cfg->fg_pixel_blending) {
+        if (cfg->fg_alpha_pix) {
+            fga = (p_fg.a >> 16) & 0xFF;
+        } else {
+            fga = (cfg->fg_alpha[p_fg.a] >> 16) & 0xFF;
+        }
+    } else {
+        fga = (cfg->fg_alpha[cfg->fg_alpha_sel] >> 16) & 0xFF;
+    }
+    if (cfg->bg_pixel_blending) {
+        if (cfg->bg_alpha_pix) {
+            bga = (p_bg.a >> 16) & 0xFF;
+        } else {
+            bga = (cfg->bg_alpha[p_bg.a] >> 16) & 0xFF;
+        }
+    } else {
+        bga = (cfg->bg_alpha[cfg->bg_alpha_sel] >> 16) & 0xFF;
+    }
+    bg = (bg * b + fg * a) / 0xFF;
+    if (bg > 0xFF) {
+        res->r = 0xFF;
+    } else {
+        res->r = bg;
+    }
+    bga = (bga * p + fga * q) / 0xFF;
+    if (bga > 0xFF) {
+        res->a |= 0xFF << 16;
+    } else {
+        res->a |= bga << 16;
+    }
+}
+
+static void blend_colorkey(DrawConfig *cfg, rgba p_bg, rgba p_fg, rgba *p)
+{
+    uint8_t r, g, b;
+
+    if (cfg->color_ctl & FIMD_WKEYCON0_CTL_KEYEN) {
+        blend_alpha(cfg, p_bg, p_fg, p);
+        return ;
+    }
+    r = ((cfg->color_key & ~cfg->color_mask) >> 16) & 0xFF;
+    g = ((cfg->color_key & ~cfg->color_mask) >>  8) & 0xFF;
+    b = ((cfg->color_key & ~cfg->color_mask) >>  0) & 0xFF;
+    if (cfg->color_ctl & FIMD_WKEYCON0_CTL_DIRCON) {
+        if ((p_fg.r & ~((cfg->color_mask >> 16) & 0xFF)) == r &&
+                (p_fg.g & ~((cfg->color_mask >>  8) & 0xFF)) == g &&
+                (p_fg.b & ~((cfg->color_mask >>  0) & 0xFF)) == b) {
+            if (cfg->color_ctl & FIMD_WKEYCON0_CTL_KEYBLEN) {
+                p_fg.a = 1;
+                cfg->fg_pixel_blending = 0;
+                blend_alpha(cfg, p_bg, p_fg, p);
+            } else {
+                *p = p_bg;
+            }
+        } else {
+            if (cfg->color_ctl & FIMD_WKEYCON0_CTL_KEYBLEN) {
+                p_fg.a = 0;
+                cfg->fg_pixel_blending = 0;
+                blend_alpha(cfg, p_bg, p_fg, p);
+            } else {
+                *p = p_fg;
+            }
+        }
+    } else {
+        if ((p_bg.r & ~((cfg->color_mask >> 16) & 0xFF)) == r &&
+                (p_bg.g & ~((cfg->color_mask >>  8) & 0xFF)) == g &&
+                (p_bg.b & ~((cfg->color_mask >>  0) & 0xFF)) == b) {
+            if (cfg->color_ctl & FIMD_WKEYCON0_CTL_KEYBLEN) {
+                p_fg.a = 1;
+                cfg->fg_pixel_blending = 0;
+                blend_alpha(cfg, p_bg, p_fg, p);
+            } else {
+                *p = p_fg;
+            }
+        } else {
+            if (cfg->color_ctl & FIMD_WKEYCON0_CTL_KEYBLEN) {
+                p_fg.a = 0;
+                cfg->fg_pixel_blending = 0;
+                blend_alpha(cfg, p_bg, p_fg, p);
+            } else {
+                *p = p_bg;
+            }
+        }
+    }
+}
+
+static inline void putpixel_by_bpp(DrawConfig *cfg, int bpp)
+{
+    switch (bpp) {
+    case 8:
+        cfg->put_pixel = put_pixel8;
+        break;
+    case 15:
+        cfg->put_pixel = put_pixel15;
+        break;
+    case 16:
+        cfg->put_pixel = put_pixel16;
+        break;
+    case 24:
+        cfg->put_pixel = put_pixel24;
+        break;
+    case 32:
+        cfg->put_pixel = put_pixel32;
+        break;
+    default:
+        hw_error("s5pc210.fimd: unsupported BPP (%d)", bpp);
+        break;
+    }
+}
+
+static void s5pc210_fimd_update_irq(S5pc210fimdState *s)
+{
+    if (!(s->vidintcon[0] & FIMD_VIDINT_INTEN)) {
+        qemu_irq_lower(s->irq[0]);
+        qemu_irq_lower(s->irq[1]);
+        qemu_irq_lower(s->irq[2]);
+        return;
+    }
+    if ((s->vidintcon[0] & FIMD_VIDINT_INTFIFOEN) &&
+            (s->vidintcon[1] & FIMD_VIDINT_INTFIFOPEND)) {
+        qemu_irq_raise(s->irq[0]);
+    } else {
+        qemu_irq_lower(s->irq[0]);
+    }
+    if ((s->vidintcon[0] & FIMD_VIDINT_INTFRMEN) &&
+            (s->vidintcon[1] & FIMD_VIDINT_INTFRMPEND)) {
+        qemu_irq_raise(s->irq[1]);
+    } else {
+        qemu_irq_lower(s->irq[1]);
+    }
+    if ((s->vidintcon[0] & FIMD_VIDINT_I80IFDONE) &&
+            (s->vidintcon[1] & FIMD_VIDINT_INTI80PEND)) {
+        qemu_irq_raise(s->irq[2]);
+    } else {
+        qemu_irq_lower(s->irq[2]);
+    }
+}
+
+static void s5pc210_update_resolution(S5pc210fimdState *s)
+{
+    /* LCD resolution is stored in VIDEO TIME CONTROL REGISTER 2 */
+    uint32_t width = ((s->vidtcon[2] >> FIMD_VIDTCON2_HOR_SHIFT) &
+            FIMD_VIDTCON2_SIZE_MASK) + 1;
+    uint32_t height = ((s->vidtcon[2] >> FIMD_VIDTCON2_VER_SHIFT) &
+            FIMD_VIDTCON2_SIZE_MASK) + 1;
+
+    if (s->ifb == NULL || ds_get_width(s->console) != width ||
+            ds_get_height(s->console) != height) {
+        print_debug1("Resolution changed from %ux%u to %ux%u\n",
+           ds_get_width(s->console), ds_get_height(s->console), width, height);
+        qemu_console_resize(s->console, width, height);
+        s->ifb = g_realloc(s->ifb, width * height * RGBA_SIZE);
+        memset(s->ifb, 0, width * height * RGBA_SIZE);
+        s->invalidate = true;
+    }
+}
+
+static void s5pc210_fimd_update(void *opaque)
+{
+    S5pc210fimdState *s = (S5pc210fimdState *)opaque;
+    DrawConfig cfg;
+    int i, line, buf_id;
+    target_phys_addr_t fb_start_addr, fb_next_line_addr, inc_size, fb_len, x;
+    int lefttop_x, lefttop_y, rightbottom_x, rightbottom_y;
+    int width, height;
+    int first_line = -1, last_line = -1, fb_line_size;
+    uint8_t is_first_window = 1;
+    uint8_t *host_fb_addr, *host_fb_start_addr;
+    bool is_dirty = false;
+    ram_addr_t pd;
+    const int global_width = (s->vidtcon[2] & FIMD_VIDTCON2_SIZE_MASK) + 1;
+    const int global_height = ((s->vidtcon[2] >> FIMD_VIDTCON2_VER_SHIFT) &
+            FIMD_VIDTCON2_SIZE_MASK) + 1;
+
+    if (!s || !s->console || !ds_get_bits_per_pixel(s->console)) {
+        return;
+    }
+
+    if (s->enabled == false) {
+        return;
+    }
+
+    memset(&cfg, 0, sizeof(cfg));
+    s5pc210_update_resolution(s);
+
+    for (i = 0; i < NUM_OF_WINDOWS; i++) {
+        if (s->window[i].wincon & FIMD_WINCON_ENWIN) {
+            cfg.fg_alpha_pix = 0;
+            s5pc210_parse_win_bppmode(&s->window[i], &cfg);
+            /* If we have mode with palletized color then we need to parse
+               palette color mode and set pixel-to-rgb conversion function
+               accordingly. */
+            if (cfg.is_palletized) {
+                uint32_t tmp = s5pc210_wxpal(s, i);
+                /* Different windows have different mapping WxPAL to palette
+                   pixel format. This transform happens to unify them all. */
+                if (i < 2 && tmp < 7) {
+                    tmp = 6 - tmp;
+                }
+                cfg.pixel_to_rgb = wxpal_to_rgb[tmp];
+                if (tmp == 7) {
+                    cfg.fg_alpha_pix = 1;
+                }
+            }
+            cfg.put_pixel = put_rgba;
+            cfg.get_pixel = get_rgba;
+            cfg.bg_alpha_pix = 1;
+            cfg.color_mask = s->window[i].keycon[0] & FIMD_WKEYCON0_COMPKEY;
+            cfg.color_key = s->window[i].keycon[1];
+            cfg.color_ctl =
+                    (s->window[i].keycon[0] >> FIMD_WKEYCON0_CTL_SHIFT) & 7;
+            if (i == 0) {
+                cfg.fg_alpha[0] = s->window[i].vidw_alpha[0];
+                cfg.fg_alpha[1] = s->window[i].vidw_alpha[1];
+            } else {
+                cfg.fg_alpha[0] =
+                    unpack_by_4((s->window[i].vidosd[2] &
+                    FIMD_VIDOSD_ALPHA_AEN0) >> FIMD_VIDOSD_AEN0_SHIFT) |
+                    (s->window[i].vidw_alpha[0] & FIMD_VIDALPHA_ALPHA_MASK);
+                cfg.fg_alpha[1] =
+                    unpack_by_4(s->window[i].vidosd[2] &
+                    FIMD_VIDOSD_ALPHA_AEN1) | (s->window[i].vidw_alpha[0]
+                    & FIMD_VIDALPHA_ALPHA_MASK);
+            }
+            cfg.bg_pixel_blending = 1;
+            cfg.fg_pixel_blending = s->window[i].wincon & FIMD_WINCON_BLD_PIX;
+            cfg.fg_alpha_sel = (s->window[i].wincon & FIMD_WINCON_ALPHA_SEL) >>
+                    FIMD_WINCON_ALSEL_SHIFT;
+            cfg.palette = s->window[i].palette;
+            cfg.swap = (s->window[i].wincon & FIMD_WINCON_SWAP) >>
+                    FIMD_WINCON_SWAP_SHIFT;
+            cfg.coef_q = coef_decode((s->window[i].blendeq >>
+                    FIMD_BLENDEQ_COEF_Q) & FIMD_BLENDEQ_COEF_MASK);
+            cfg.coef_p = coef_decode((s->window[i].blendeq >>
+                    FIMD_BLENDEQ_COEF_P) & FIMD_BLENDEQ_COEF_MASK);
+            cfg.coef_b = coef_decode((s->window[i].blendeq >>
+                    FIMD_BLENDEQ_COEF_B) & FIMD_BLENDEQ_COEF_MASK);
+            cfg.coef_a = coef_decode((s->window[i].blendeq >>
+                    FIMD_BLENDEQ_COEF_A) & FIMD_BLENDEQ_COEF_MASK);
+            if (is_first_window) {
+                cfg.blend = NULL;
+            } else {
+                cfg.blend = blend_colorkey;
+            }
+            is_first_window = 0;
+            if (s->window[i].winmap & FIMD_WINMAP_EN) {
+                cfg.map_color = s->window[i].winmap & FIMD_WINMAP_COLOR_MASK;
+                cfg.draw_line = draw_line_mapcolor;
+            }
+
+            /* At this point CFG is fully set up except WIDTH. We can proceed
+               with drawing. */
+            lefttop_x = (s->window[i].vidosd[0] >> FIMD_VIDOSD_HOR_SHIFT)
+                    & FIMD_VIDOSD_COORD_MASK;
+            lefttop_y = (s->window[i].vidosd[0] >> FIMD_VIDOSD_VER_SHIFT)
+                    & FIMD_VIDOSD_COORD_MASK;
+            rightbottom_x = (s->window[i].vidosd[1] >> FIMD_VIDOSD_HOR_SHIFT)
+                    & FIMD_VIDOSD_COORD_MASK;
+            rightbottom_y = (s->window[i].vidosd[1] >> FIMD_VIDOSD_VER_SHIFT)
+                    & FIMD_VIDOSD_COORD_MASK;
+            height = rightbottom_y - lefttop_y + 1;
+            width = rightbottom_x - lefttop_x + 1;
+            cfg.width = width;
+            buf_id = s5pc210_buffer_status(&s->window[i]);
+
+            fb_line_size = s->window[i].buf_size & FIMD_VIDWADD2_PAGEWIDTH;
+            fb_start_addr = s->window[i].buf_start[buf_id];
+            inc_size = (s->window[i].buf_size & FIMD_VIDWADD2_PAGEWIDTH) +
+                    ((s->window[i].buf_size >> FIMD_VIDWADD2_OFFSIZE_SHIFT) &
+                            FIMD_VIDWADD2_OFFSIZE);
+            fb_len = inc_size * height;
+
+            cpu_physical_sync_dirty_bitmap(fb_start_addr,
+                                              fb_start_addr + fb_len);
+            host_fb_addr = cpu_physical_memory_map(fb_start_addr, &fb_len, 0);
+            if (!host_fb_addr) {
+                return;
+            }
+            if (fb_len != inc_size * height) {
+                cpu_physical_memory_unmap(host_fb_addr, fb_len, 0, 0);
+                return;
+            }
+
+            host_fb_start_addr = host_fb_addr;
+            for (line = 0; line < height; line++) {
+                fb_next_line_addr = fb_start_addr + fb_line_size;
+                for (x = fb_start_addr; x < fb_next_line_addr;
+                    x += TARGET_PAGE_SIZE) {
+                    pd = (cpu_get_physical_page_desc(x) & TARGET_PAGE_MASK) +
+                            (x & ~TARGET_PAGE_MASK);
+                    is_dirty = is_dirty ||
+                            cpu_physical_memory_get_dirty(pd, VGA_DIRTY_FLAG);
+                }
+
+                if (s->invalidate || is_dirty) {
+                    if (first_line == -1) {
+                        first_line = line;
+                    }
+                    last_line = line;
+                    cfg.draw_line(&cfg, host_fb_addr,
+                          s->ifb + lefttop_x * RGBA_SIZE +
+                              (lefttop_y + line) * global_width * RGBA_SIZE,
+                          s->ifb + lefttop_x * RGBA_SIZE +
+                              (lefttop_y + line) * global_width * RGBA_SIZE);
+                }
+                host_fb_addr += inc_size;
+                is_dirty = false;
+                fb_start_addr += inc_size;
+            }
+            cpu_physical_memory_unmap(host_fb_start_addr, fb_len, 0, 0);
+            fb_start_addr = s->window[i].buf_start[buf_id];
+            pd = (cpu_get_physical_page_desc(fb_start_addr) & TARGET_PAGE_MASK)+
+                             (fb_start_addr & ~TARGET_PAGE_MASK);
+            cpu_physical_memory_reset_dirty(pd, pd + inc_size * height,
+                                             VGA_DIRTY_FLAG);
+        }
+    }
+    /* Last pass: copy resulting image to QEMU_CONSOLE. */
+    if (first_line >= 0) {
+        uint8_t *d;
+        int bpp;
+
+        cfg.get_pixel = get_rgba;
+        bpp = ds_get_bits_per_pixel(s->console);
+        putpixel_by_bpp(&cfg, bpp);
+        bpp = (bpp + 1) >> 3;
+        d = ds_get_data(s->console);
+        for (line = first_line; line < last_line; line++) {
+            draw_line_copy(&cfg, s->ifb + global_width * line * RGBA_SIZE,
+                           d + global_width * line * bpp);
+        }
+        dpy_update(s->console, 0, 0, global_width, global_height);
+    }
+    s->invalidate = false;
+    s->vidintcon[1] |= FIMD_VIDINT_INTFRMPEND;
+    if ((s->vidcon[0] & FIMD_VIDCON0_ENVID_F) == 0) {
+        s5pc210_fimd_enable(s, false);
+    }
+    s5pc210_fimd_update_irq(s);
+}
+
+static void s5pc210_fimd_invalidate(void *opaque)
+{
+    S5pc210fimdState *s = (S5pc210fimdState *)opaque;
+    s->invalidate = true;
+}
+
+static void s5pc210_fimd_reset(DeviceState *d)
+{
+    S5pc210fimdState *s = container_of(d, S5pc210fimdState, busdev.qdev);
+    int i;
+    unsigned long begin = (unsigned long)s + offsetof(S5pc210fimdState, vidcon);
+    unsigned long len = ((unsigned long)s + offsetof(S5pc210fimdState, window))
+            - begin;
+    s5pc210_fimd_trace_reset();
+
+    /* Set all display controller registers to 0 */
+    memset((void *)begin, 0, len);
+    for (i = 0; i < NUM_OF_WINDOWS; i++) {
+        memset(&s->window[i], 0, sizeof(S5pc210fimdWindow));
+        s->window[i].blendeq = 0xC2;
+    }
+
+    if (s->ifb != NULL) {
+        g_free(s->ifb);
+    }
+    s->ifb = NULL;
+
+    s->invalidate = true;
+    s5pc210_fimd_enable(s, false);
+    /* Some registers have non-zero initial values */
+    s->winchmap = 0x7D517D51;
+    s->colorgaincon = 0x10040100;
+    s->huecoef_cr[0] = s->huecoef_cr[3] = 0x01000100;
+    s->huecoef_cb[0] = s->huecoef_cb[3] = 0x01000100;
+    s->hueoffset = 0x01800080;
+}
+
+static void s5pc210_fimd_write(void *opaque, target_phys_addr_t offset,
+                              uint64_t val, unsigned size)
+{
+    S5pc210fimdState *s = (S5pc210fimdState *)opaque;
+    int w, i;
+
+    print_debug2("write offset 0x%08x, value=%ld(0x%08lx)\n", offset, val, val);
+
+    if (offset == FIMD_VIDCON0) {
+        if ((val & FIMD_VIDCON0_ENVID_MASK) == FIMD_VIDCON0_ENVID_MASK) {
+            s5pc210_fimd_enable(s, true);
+        } else {
+            if ((val & FIMD_VIDCON0_ENVID) == 0) {
+                s5pc210_fimd_enable(s, false);
+            }
+        }
+        s->vidcon[0] = val;
+    } else if (offset == FIMD_VIDCON1) {
+        /* Leave read-only bits as is */
+        val = (val & (~FIMD_VIDCON1_ROMASK)) |
+                (s->vidcon[1] & FIMD_VIDCON1_ROMASK);
+        s->vidcon[1] = val;
+    } else if (offset >= FIMD_VIDCON2 && offset <= FIMD_VIDCON3) {
+        s->vidcon[(offset) >> 2] = val;
+    } else if (offset >= 0x010 && offset <= 0x01C) {
+        s->vidtcon[(offset - 0x010) >> 2] = val;
+    } else if (offset >= 0x020 && offset <= 0x030) {
+        w = (offset - 0x020) >> 2;
+        val = (val & ~FIMD_WINCON_ROMASK) |
+                (s->window[w].wincon & FIMD_WINCON_ROMASK);
+        s5pc210_fimd_trace_bppmode(&s->window[w], w, val);
+        switch (val & FIMD_WINCON_BUFSELECT) {
+        case FIMD_WINCON_BUF0_SEL:
+            val &= ~FIMD_WINCON_BUFSTATUS;
+            break;
+        case FIMD_WINCON_BUF1_SEL:
+            val = (val & ~FIMD_WINCON_BUFSTATUS_H) |
+                FIMD_WINCON_BUFSTATUS_L;
+            break;
+        case FIMD_WINCON_BUF2_SEL:
+            if (val & FIMD_WINCON_BUFMODE) {
+                val = (val & ~FIMD_WINCON_BUFSTATUS_L) |
+                    FIMD_WINCON_BUFSTATUS_H;
+            }
+            break;
+        default:
+            break;
+        }
+        s->window[w].wincon = val;
+    } else if (offset == 0x034) {
+        s->shadowcon = val;
+    } else if (offset == 0x03C) {
+        s->winchmap = val;
+    } else if (offset >= 0x040 && offset <= 0x088) {
+        w = (offset - 0x040) >> 4;
+        i = ((offset - 0x040) & 0xF) >> 2;
+        if (i == 3 && w != 1 && w != 2) {
+            print_error("Bad write offset 0x%08x\n", offset);
+        }
+        s->window[w].vidosd[i] = val;
+    } else if (offset >= 0x0A0 && offset <= 0x0C4) {
+        w = (offset - 0x0A0) >> 3;
+        i = ((offset - 0x0A0) >> 2) & 1;
+        s->window[w].buf_start[i] = val;
+    } else if (offset >= 0x0D0 && offset <= 0x0F4) {
+        w = (offset - 0x0D0) >> 3;
+        i = ((offset - 0x0D0) >> 2) & 1;
+        s->window[w].buf_end[i] = val;
+    } else if (offset >= 0x100 && offset <= 0x110) {
+        s->window[(offset - 0x100) >> 2].buf_size = val;
+    } else if (offset == 0x130) {
+        s->vidintcon[0] = val;
+    } else if (offset == 0x134) {
+        s->vidintcon[1] &= ~(val & 7);
+        s5pc210_fimd_update_irq(s);
+    } else if (offset >= 0x140 && offset <= 0x15C) {
+        w = ((offset - 0x140) >> 3) + 1;
+        i = ((offset - 0x140) >> 2) & 1;
+        s->window[w].keycon[i] = val;
+    } else if (offset >= 0x160 && offset <= 0x16C) {
+        w = ((offset - 0x160) >> 2) + 1;
+        s->window[w].keyalpha = val;
+    } else if (offset == 0x170) {
+        s->dithmode = val;
+    } else if (offset >= 0x180 && offset <= 0x190) {
+        if (val & FIMD_WINMAP_EN) {
+            s->invalidate = true;
+            s5pc210_fimd_update(s);
+        }
+        s->window[(offset - 0x180) >> 2].winmap = val;
+    } else if (offset >= 0x19C && offset <= 0x1A0) {
+        s->wpalcon[(offset - 0x19C) >> 2] = val;
+    } else if (offset == 0x1A4) {
+        val = (val & ~FIMD_TRIGCON_ROMASK) | (s->trigcon & FIMD_TRIGCON_ROMASK);
+        s->trigcon = val;
+    } else if (offset >= 0x1B0 && offset <= 0x1BC) {
+        s->i80ifcon[(offset - 0x1B0) >> 2] = val;
+    } else if (offset == 0x1C0) {
+        s->colorgaincon = val;
+    } else if (offset >= 0x1D0 && offset <= 0x1D4) {
+        s->ldi_cmdcon[(offset - 0x1D0) >> 2] = val;
+    } else if (offset >= 0x1E0 && offset <= 0x1E8) {
+        i = (offset - 0x1E0) >> 2;
+        if (i != 2) {
+            s->sifccon[i] = val;
+        }
+    } else if (offset >= 0x1EC && offset <= 0x1F8) {
+        i = (offset - 0x1EC) >> 2;
+        s->huecoef_cr[i] = val;
+    } else if (offset >= 0x1FC && offset <= 0x208) {
+        i = (offset - 0x1FC) >> 2;
+        s->huecoef_cb[i] = val;
+    } else if (offset == 0x20C) {
+        s->hueoffset = val;
+    } else if (offset >= 0x21C && offset <= 0x240) {
+        w = ((offset - 0x21C) >> 3);
+        i = ((offset - 0x21C) >> 2) & 1;
+        s->window[w].vidw_alpha[i] = val;
+    } else if (offset >= 0x244 && offset <= 0x250) {
+        s->window[(offset - 0x244) >> 2].blendeq = val;
+    } else if (offset == 0x260) {
+        s->blendcon = val;
+    } else if (offset >= 0x264 && offset <= 0x274) {
+        s->window[(offset - 0x264) >> 2].rtqoscon = val;
+    } else if (offset == 0x27C) {
+        s->dualrgb = val;
+    } else if (offset >= 0x280 && offset <= 0x2AC) {
+        s->i80ifcmd[(offset - 0x280) >> 2] = val;
+    } else if (offset >= 0x40A0 && offset <= 0x40C0) {
+        if (offset & 0x0004) {
+            print_error("bad write offset 0x%08x\n", offset);
+        }
+        s->window[(offset - 0x40A0) >> 3].shadow_buf_start = val;
+    } else if (offset >= 0x40D0 && offset <= 0x40F0) {
+        if (offset & 0x0004) {
+            print_error("bad write offset 0x%08x\n", offset);
+        }
+        s->window[(offset - 0x40D0) >> 3].shadow_buf_end = val;
+    } else if (offset >= 0x4100 && offset <= 0x4110) {
+        s->window[(offset - 0x4100) >> 2].shadow_buf_size = val;
+    } else if (offset >= 0x2400 && offset <= 0x37FC) {
+        w = (offset - 0x2400) >> 10;
+        i = ((offset - 0x2400) >> 2) & 0xFF;
+        s->window[w].palette[i] = val;
+    } else if (offset >= 0x0400 && offset <= 0x0BFC) {
+        w = (offset - 0x0400) >> 10;
+        i = ((offset - 0x0400) >> 2) & 0xFF;
+        s->window[w].palette[i] = val;
+    } else {
+        print_error("bad write offset 0x%08x\n", offset);
+    }
+}
+
+static uint64_t s5pc210_fimd_read(void *opaque, target_phys_addr_t offset,
+                                  unsigned size)
+{
+    S5pc210fimdState *s = (S5pc210fimdState *)opaque;
+    int w, i;
+
+    print_debug2("read offset 0x%08x\n", offset);
+
+    if (offset <= 0x00C) {
+        return s->vidcon[(offset) >> 2];
+    } else if (offset >= 0x010 && offset <= 0x01C) {
+        return s->vidtcon[(offset - 0x010) >> 2];
+    } else if (offset >= 0x020 && offset <= 0x030) {
+        return s->window[(offset - 0x020) >> 2].wincon;
+    } else if (offset == 0x034) {
+        return s->shadowcon;
+    } else if (offset == 0x03C) {
+        return s->winchmap;
+    } else if (offset >= 0x040 && offset <= 0x088) {
+        w = (offset - 0x040) >> 4;
+        i = ((offset - 0x040) & 0xF) >> 2;
+        if (i == 3 && w != 1 && w != 2) {
+            goto return_error;
+        }
+        return s->window[w].vidosd[i];
+    } else if (offset >= 0x0A0 && offset <= 0x0C4) {
+        w = (offset - 0x0A0) >> 3;
+        i = ((offset - 0x0A0) >> 2) & 1;
+        return s->window[w].buf_start[i];
+    } else if (offset >= 0x0D0 && offset <= 0x0F4) {
+        w = (offset - 0x0D0) >> 3;
+        i = ((offset - 0x0D0) >> 2) & 1;
+        return s->window[w].buf_end[i];
+    } else if (offset >= 0x100 && offset <= 0x110) {
+        return s->window[(offset - 0x100) >> 2].buf_size;
+    } else if (offset >= 0x130 && offset <= 0x134) {
+        return s->vidintcon[(offset - 0x130) >> 2];
+    } else if (offset >= 0x140 && offset <= 0x15C) {
+        w = ((offset - 0x140) >> 3) + 1;
+        i = ((offset - 0x140) >> 2) & 1;
+        return s->window[w].keycon[i];
+    } else if (offset >= 0x160 && offset <= 0x16C) {
+        w = ((offset - 0x160) >> 2) + 1;
+        return s->window[w].keyalpha;
+    } else if (offset == 0x170) {
+        return s->dithmode;
+    } else if (offset >= 0x180 && offset <= 0x190) {
+        return s->window[(offset - 0x180) >> 2].winmap;
+    } else if (offset >= 0x19C && offset <= 0x1A0) {
+        return s->wpalcon[(offset - 0x19C) >> 2];
+    } else if (offset == 0x1A4) {
+        return s->trigcon;
+    } else if (offset >= 0x1B0 && offset <= 0x1BC) {
+        return s->i80ifcon[(offset - 0x1B0) >> 2];
+    } else if (offset == 0x1C0) {
+        return s->colorgaincon;
+    } else if (offset >= 0x1D0 && offset <= 0x1D4) {
+        return s->ldi_cmdcon[(offset - 0x1D0) >> 2];
+    } else if (offset >= 0x1E0 && offset <= 0x1E8) {
+        i = (offset - 0x1E0) >> 2;
+        return s->sifccon[i];
+    } else if (offset >= 0x1EC && offset <= 0x1F8) {
+        i = (offset - 0x1EC) >> 2;
+        return s->huecoef_cr[i];
+    } else if (offset >= 0x1FC && offset <= 0x208) {
+        i = (offset - 0x1FC) >> 2;
+        return s->huecoef_cb[i];
+    } else if (offset == 0x20C) {
+        return s->hueoffset;
+    } else if (offset >= 0x21C && offset <= 0x240) {
+        w = ((offset - 0x21C) >> 3);
+        i = ((offset - 0x21C) >> 2) & 1;
+        return s->window[w].vidw_alpha[i];
+    } else if (offset >= 0x244 && offset <= 0x250) {
+        return s->window[(offset - 0x244) >> 2].blendeq;
+    } else if (offset == 0x260) {
+        return s->blendcon;
+    } else if (offset >= 0x264 && offset <= 0x274) {
+        return s->window[(offset - 0x264) >> 2].rtqoscon;
+    } else if (offset == 0x27C) {
+        return s->dualrgb;
+    } else if (offset >= 0x280 && offset <= 0x2AC) {
+        return s->i80ifcmd[(offset - 0x280) >> 2];
+    } else if (offset >= 0x40A0 && offset <= 0x40C0) {
+        if (offset & 0x0004) {
+            goto return_error;
+        }
+        return s->window[(offset - 0x40A0) >> 3].shadow_buf_start;
+    } else if (offset >= 0x40D0 && offset <= 0x40F0) {
+        if (offset & 0x0004) {
+            goto return_error;
+        }
+        return s->window[(offset - 0x40D0) >> 3].shadow_buf_end;
+    } else if (offset >= 0x4100 && offset <= 0x4110) {
+        return s->window[(offset - 0x4100) >> 2].shadow_buf_size;
+    } else if (offset >= 0x2400 && offset <= 0x37FC) {
+        w = (offset - 0x2400) >> 10;
+        i = ((offset - 0x2400) >> 2) & 0xFF;
+        return s->window[w].palette[i];
+    } else if (offset >= 0x0400 && offset <= 0x0BFC) {
+        /* Palete aliases for win 0,1 */
+        w = (offset - 0x0400) >> 10;
+        i = ((offset - 0x0400) >> 2) & 0xFF;
+        return s->window[w].palette[i];
+    }
+return_error:
+    print_error("bad read offset 0x%08x\n", offset);
+    return 0xBAADBAAD;
+}
+
+static const MemoryRegionOps s5pc210_fimd_mmio_ops = {
+    .read = s5pc210_fimd_read,
+    .write = s5pc210_fimd_write,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false
+    },
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int s5pc210_fimd_load(void *opaque, int version_id)
+{
+    S5pc210fimdState *s = (S5pc210fimdState *)opaque;
+
+    if (version_id != 1) {
+        return -EINVAL;
+    }
+
+    /* Redraw the whole screen */
+    s5pc210_update_resolution(s);
+    s5pc210_fimd_invalidate(s);
+    s5pc210_fimd_enable(s, (s->vidcon[0] & FIMD_VIDCON0_ENVID_MASK) ==
+            FIMD_VIDCON0_ENVID_MASK);
+    return 0;
+}
+
+static const VMStateDescription s5pc210_fimd_window_vmstate = {
+    .name = "s5pc210.fimd_window",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields      = (VMStateField[]) {
+        VMSTATE_UINT32(wincon, S5pc210fimdWindow),
+        VMSTATE_UINT32_ARRAY(vidosd, S5pc210fimdWindow, 4),
+        VMSTATE_UINT32_ARRAY(buf_start, S5pc210fimdWindow, 3),
+        VMSTATE_UINT32_ARRAY(buf_end, S5pc210fimdWindow, 3),
+        VMSTATE_UINT32(buf_size, S5pc210fimdWindow),
+        VMSTATE_UINT32_ARRAY(keycon, S5pc210fimdWindow, 2),
+        VMSTATE_UINT32(keyalpha, S5pc210fimdWindow),
+        VMSTATE_UINT32(winmap, S5pc210fimdWindow),
+        VMSTATE_UINT32_ARRAY(vidw_alpha, S5pc210fimdWindow, 2),
+        VMSTATE_UINT32(blendeq, S5pc210fimdWindow),
+        VMSTATE_UINT32(rtqoscon, S5pc210fimdWindow),
+        VMSTATE_UINT32_ARRAY(palette, S5pc210fimdWindow, 256),
+        VMSTATE_UINT32(shadow_buf_start, S5pc210fimdWindow),
+        VMSTATE_UINT32(shadow_buf_end, S5pc210fimdWindow),
+        VMSTATE_UINT32(shadow_buf_size, S5pc210fimdWindow),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const VMStateDescription s5pc210_fimd_vmstate = {
+    .name = "s5pc210.fimd",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .post_load = s5pc210_fimd_load,
+    .fields      = (VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(vidcon, S5pc210fimdState, 4),
+        VMSTATE_UINT32_ARRAY(vidtcon, S5pc210fimdState, 4),
+        VMSTATE_UINT32(shadowcon, S5pc210fimdState),
+        VMSTATE_UINT32(winchmap, S5pc210fimdState),
+        VMSTATE_UINT32_ARRAY(vidintcon, S5pc210fimdState, 2),
+        VMSTATE_UINT32(dithmode, S5pc210fimdState),
+        VMSTATE_UINT32_ARRAY(wpalcon, S5pc210fimdState, 2),
+        VMSTATE_UINT32(trigcon, S5pc210fimdState),
+        VMSTATE_UINT32_ARRAY(i80ifcon, S5pc210fimdState, 4),
+        VMSTATE_UINT32(colorgaincon, S5pc210fimdState),
+        VMSTATE_UINT32_ARRAY(ldi_cmdcon, S5pc210fimdState, 2),
+        VMSTATE_UINT32_ARRAY(sifccon, S5pc210fimdState, 3),
+        VMSTATE_UINT32_ARRAY(huecoef_cr, S5pc210fimdState, 4),
+        VMSTATE_UINT32_ARRAY(huecoef_cb, S5pc210fimdState, 4),
+        VMSTATE_UINT32(hueoffset, S5pc210fimdState),
+        VMSTATE_UINT32(blendcon, S5pc210fimdState),
+        VMSTATE_UINT32(dualrgb, S5pc210fimdState),
+        VMSTATE_UINT32_ARRAY(i80ifcmd, S5pc210fimdState, 12),
+        VMSTATE_STRUCT_ARRAY(window, S5pc210fimdState, 5, 1,
+                s5pc210_fimd_window_vmstate, S5pc210fimdWindow),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static int s5pc210_fimd_init(SysBusDevice *dev)
+{
+    S5pc210fimdState *s = FROM_SYSBUS(S5pc210fimdState, dev);
+
+    s->ifb = NULL;
+
+    sysbus_init_irq(dev, &s->irq[0]);
+    sysbus_init_irq(dev, &s->irq[1]);
+    sysbus_init_irq(dev, &s->irq[2]);
+
+    memory_region_init_io(&s->iomem, &s5pc210_fimd_mmio_ops, s, "s5pc210.fimd",
+            FIMD_REGS_SIZE);
+    sysbus_init_mmio_region(dev, &s->iomem);
+    s->console = graphic_console_init(s5pc210_fimd_update,
+                                      s5pc210_fimd_invalidate, NULL, NULL, s);
+
+    return 0;
+}
+
+static SysBusDeviceInfo s5pc210_fimd_info = {
+    .init = s5pc210_fimd_init,
+    .qdev.name  = "s5pc210.fimd",
+    .qdev.size  = sizeof(S5pc210fimdState),
+    .qdev.vmsd  = &s5pc210_fimd_vmstate,
+    .qdev.reset = s5pc210_fimd_reset,
+};
+
+static void s5pc210_fimd_register_devices(void)
+{
+    sysbus_register_withprop(&s5pc210_fimd_info);
+}
+
+device_init(s5pc210_fimd_register_devices)