diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
index 8498116..5175a9d 100644
--- a/hw/arm/Makefile.objs
+++ b/hw/arm/Makefile.objs
@@ -49,3 +49,4 @@ obj-y += fti2c010.o
 obj-y += ftssp010.o
 obj-y += ftmac110.o
 obj-y += ftgmac100.o
+obj-y += ftlcdc200.o
diff --git a/hw/arm/faraday_a360.c b/hw/arm/faraday_a360.c
index 51e8649..d3df30e 100644
--- a/hw/arm/faraday_a360.c
+++ b/hw/arm/faraday_a360.c
@@ -132,6 +132,9 @@ a360_device_init(A360State *s)
             done_nic = 1;
         }
     }
+
+    /* ftlcdc200 */
+    sysbus_create_simple("ftlcdc200", 0x90600000, pic[27]);
 }
 
 static void
diff --git a/hw/arm/faraday_a369.c b/hw/arm/faraday_a369.c
index 65dda61..9100616 100644
--- a/hw/arm/faraday_a369.c
+++ b/hw/arm/faraday_a369.c
@@ -183,6 +183,16 @@ a369_device_init(A369State *s)
             done_nic = 1;
         }
     }
+
+    /* ftlcdc200 */
+    sysbus_create_varargs("ftlcdc200",
+                          0x94a00000,
+                          pic[0],  /* ALL (NC in A369) */
+                          pic[25], /* VSTATUS */
+                          pic[24], /* Base Address Update */
+                          pic[23], /* FIFO Under-Run */
+                          pic[22], /* AHB Bus Error */
+                          NULL);
 }
 
 static void
diff --git a/hw/arm/ftlcdc200.c b/hw/arm/ftlcdc200.c
new file mode 100644
index 0000000..95a596d
--- /dev/null
+++ b/hw/arm/ftlcdc200.c
@@ -0,0 +1,505 @@
+/*
+ * Faraday FTLCDC200 Color LCD Controller
+ *
+ * base is pl110.c
+ *
+ * Copyright (c) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com>
+ *
+ * This code is licensed under the GNU LGPL
+ */
+
+#include <hw/sysbus.h>
+#include <hw/framebuffer.h>
+#include <ui/console.h>
+#include <ui/pixel_ops.h>
+
+#include "ftlcdc200.h"
+
+enum ftlcdc200_irqpin {
+    IRQ_ALL = 0,
+    IRQ_VSTATUS,
+    IRQ_BASEUPT,
+    IRQ_FIFOUR,
+    IRQ_BUSERR,
+};
+
+enum ftlcdc200_bppmode {
+    BPP_1 = 0,
+    BPP_2,
+    BPP_4,
+    BPP_8,
+    BPP_16,
+    BPP_32,
+    BPP_16_565,
+    BPP_12,
+};
+
+#define TYPE_FTLCDC200  "ftlcdc200"
+
+typedef struct Ftlcdc200State {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    DisplayState *ds;
+    qemu_irq irq[5];
+    int cols;
+    int rows;
+    enum ftlcdc200_bppmode bpp;
+    int invalidate;
+    uint32_t palette[256];
+    uint32_t raw_palette[128];
+
+    /* hw register caches */
+
+    uint32_t fer;   /* function enable register */
+    uint32_t ppr;   /* panel pixel register */
+    uint32_t ier;   /* interrupt enable register */
+    uint32_t isr;   /* interrupt status register */
+    uint32_t sppr;  /* serail panel pixel register */
+
+    uint32_t fb[4]; /* frame buffer base address register */
+    uint32_t ht;    /* horizontal timing control register */
+    uint32_t vt0;   /* vertital timing control register 0 */
+    uint32_t vt1;   /* vertital timing control register 1 */
+    uint32_t pol;   /* polarity */
+
+} Ftlcdc200State;
+
+#define FTLCDC200(obj) \
+    OBJECT_CHECK(Ftlcdc200State, obj, TYPE_FTLCDC200)
+
+static const VMStateDescription vmstate_ftlcdc200 = {
+    .name = TYPE_FTLCDC200,
+    .version_id = 2,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_INT32(cols, Ftlcdc200State),
+        VMSTATE_INT32(rows, Ftlcdc200State),
+        VMSTATE_UINT32(bpp, Ftlcdc200State),
+        VMSTATE_INT32(invalidate, Ftlcdc200State),
+        VMSTATE_UINT32_ARRAY(palette, Ftlcdc200State, 256),
+        VMSTATE_UINT32_ARRAY(raw_palette, Ftlcdc200State, 128),
+        VMSTATE_UINT32(fer, Ftlcdc200State),
+        VMSTATE_UINT32(ppr, Ftlcdc200State),
+        VMSTATE_UINT32(ier, Ftlcdc200State),
+        VMSTATE_UINT32(isr, Ftlcdc200State),
+        VMSTATE_UINT32(sppr, Ftlcdc200State),
+        VMSTATE_UINT32_ARRAY(fb, Ftlcdc200State, 4),
+        VMSTATE_UINT32(ht, Ftlcdc200State),
+        VMSTATE_UINT32(vt0, Ftlcdc200State),
+        VMSTATE_UINT32(vt1, Ftlcdc200State),
+        VMSTATE_UINT32(pol, Ftlcdc200State),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+#define BITS 8
+#include "ftlcdc200_template.h"
+#define BITS 15
+#include "ftlcdc200_template.h"
+#define BITS 16
+#include "ftlcdc200_template.h"
+#define BITS 24
+#include "ftlcdc200_template.h"
+#define BITS 32
+#include "ftlcdc200_template.h"
+
+static int ftlcdc200_enabled(Ftlcdc200State *s)
+{
+    uint32_t mask = FER_EN | FER_ON;
+    return ((s->fer & mask) == mask)
+            && s->bpp && s->cols && s->rows && s->fb[0];
+}
+
+/* Update interrupts.  */
+static void ftlcdc200_update_irq(Ftlcdc200State *s)
+{
+    uint32_t mask = s->ier & s->isr;
+
+    if (mask) {
+        qemu_irq_raise(s->irq[IRQ_ALL]);
+        qemu_set_irq(s->irq[IRQ_FIFOUR],  (mask & 0x01) ? 1 : 0);
+        qemu_set_irq(s->irq[IRQ_BASEUPT], (mask & 0x02) ? 1 : 0);
+        qemu_set_irq(s->irq[IRQ_VSTATUS], (mask & 0x04) ? 1 : 0);
+        qemu_set_irq(s->irq[IRQ_BUSERR],  (mask & 0x08) ? 1 : 0);
+    } else {
+        qemu_irq_lower(s->irq[IRQ_ALL]);
+        qemu_irq_lower(s->irq[IRQ_VSTATUS]);
+        qemu_irq_lower(s->irq[IRQ_BASEUPT]);
+        qemu_irq_lower(s->irq[IRQ_FIFOUR]);
+        qemu_irq_lower(s->irq[IRQ_BUSERR]);
+    }
+}
+
+static void ftlcdc200_update_display(void *opaque)
+{
+    Ftlcdc200State *s = FTLCDC200(opaque);
+    drawfn *fntable;
+    drawfn fn;
+    int dest_width;
+    int src_width;
+    int bpp_offset;
+    int first;
+    int last;
+
+    if (!ftlcdc200_enabled(s)) {
+        return;
+    }
+
+    switch (ds_get_bits_per_pixel(s->ds)) {
+    case 0:
+        return;
+    case 8:
+        fntable = ftlcdc200_draw_fn_8;
+        dest_width = 1;
+        break;
+    case 15:
+        fntable = ftlcdc200_draw_fn_15;
+        dest_width = 2;
+        break;
+    case 16:
+        fntable = ftlcdc200_draw_fn_16;
+        dest_width = 2;
+        break;
+    case 24:
+        fntable = ftlcdc200_draw_fn_24;
+        dest_width = 3;
+        break;
+    case 32:
+        fntable = ftlcdc200_draw_fn_32;
+        dest_width = 4;
+        break;
+    default:
+        hw_error("ftlcdc200: Bad color depth\n");
+        exit(1);
+    }
+
+#if 1
+    bpp_offset = 0;
+    fn = fntable[s->bpp + bpp_offset];
+#else
+    if (s->ppr & PPR_BGR) {
+        bpp_offset = 0;
+    } else {
+        bpp_offset = 24;
+    }
+    if ((s->ppr & PPR_ENDIAN_MASK) == PPR_ENDIAN_BBBP) {
+        fn = fntable[s->bpp + 8 + bpp_offset];
+    } else {
+        fn = fntable[s->bpp + bpp_offset];
+    }
+#endif
+
+    src_width = s->cols;
+    switch (s->bpp) {
+    case BPP_1:
+        src_width >>= 3;
+        break;
+    case BPP_2:
+        src_width >>= 2;
+        break;
+    case BPP_4:
+        src_width >>= 1;
+        break;
+    case BPP_8:
+        break;
+    case BPP_16:
+    case BPP_16_565:
+    case BPP_12:
+        src_width <<= 1;
+        break;
+    case BPP_32:
+        src_width <<= 2;
+        break;
+    }
+    dest_width *= s->cols;
+    first = 0;
+    framebuffer_update_display(s->ds, sysbus_address_space(&s->busdev),
+                               s->fb[0], s->cols, s->rows,
+                               src_width, dest_width, 0,
+                               s->invalidate,
+                               fn, s->palette,
+                               &first, &last);
+    if (s->ier & (IER_VCOMP | IER_NEXTFB)) {
+        s->isr |= (IER_VCOMP | IER_NEXTFB);
+        ftlcdc200_update_irq(s);
+    }
+    if (first >= 0) {
+        dpy_gfx_update(s->ds, 0, first, s->cols, last - first + 1);
+    }
+    s->invalidate = 0;
+}
+
+static void ftlcdc200_invalidate_display(void *opaque)
+{
+    Ftlcdc200State *s = FTLCDC200(opaque);
+    s->invalidate = 1;
+    if (ftlcdc200_enabled(s)) {
+        qemu_console_resize(s->ds, s->cols, s->rows);
+    }
+}
+
+static void ftlcdc200_update_palette(Ftlcdc200State *s, int n)
+{
+    int i;
+    uint32_t raw;
+    unsigned int r, g, b;
+
+    raw = s->raw_palette[n];
+    n <<= 1;
+    for (i = 0; i < 2; i++) {
+        r = (raw & 0x1f) << 3;
+        raw >>= 5;
+        g = (raw & 0x1f) << 3;
+        raw >>= 5;
+        b = (raw & 0x1f) << 3;
+        /* The I bit is ignored.  */
+        raw >>= 6;
+        switch (ds_get_bits_per_pixel(s->ds)) {
+        case 8:
+            s->palette[n] = rgb_to_pixel8(r, g, b);
+            break;
+        case 15:
+            s->palette[n] = rgb_to_pixel15(r, g, b);
+            break;
+        case 16:
+            s->palette[n] = rgb_to_pixel16(r, g, b);
+            break;
+        case 24:
+        case 32:
+            s->palette[n] = rgb_to_pixel32(r, g, b);
+            break;
+        }
+        n++;
+    }
+}
+
+static void ftlcdc200_resize(Ftlcdc200State *s, int width, int height)
+{
+    if (width != s->cols || height != s->rows) {
+        if (ftlcdc200_enabled(s)) {
+            qemu_console_resize(s->ds, width, height);
+        }
+    }
+    s->cols = width;
+    s->rows = height;
+}
+
+static uint64_t ftlcdc200_mem_read(void *opaque, hwaddr addr, unsigned size)
+{
+    Ftlcdc200State *s = FTLCDC200(opaque);
+
+    switch (addr) {
+    case REG_FER:
+        return s->fer;
+    case REG_PPR:
+        return s->ppr;
+    case REG_IER:
+        return s->ier;
+    case REG_ISR:
+        return s->isr;
+    case REG_FB0:
+        return s->fb[0];
+    case REG_FB1:
+        return s->fb[1];
+    case REG_FB2:
+        return s->fb[2];
+    case REG_FB3:
+        return s->fb[3];
+    case REG_HT:
+        return s->ht;
+    case REG_VT0:
+        return s->vt0;
+    case REG_VT1:
+        return s->vt1;
+    case REG_POL:
+        return s->pol;
+    case REG_SPPR:
+        return s->sppr;
+    default:
+        return 0;
+    }
+}
+
+static void ftlcdc200_mem_write(void    *opaque,
+                                hwaddr   addr,
+                                uint64_t val,
+                                unsigned size)
+{
+    Ftlcdc200State *s = FTLCDC200(opaque);
+    int n;
+
+    /* For simplicity invalidate the display whenever a control register
+       is written to.  */
+    s->invalidate = 1;
+
+    if (addr >= 0xA00 && addr < 0xC00) {
+        /* Palette.  */
+        n = (addr - 0xA00) >> 2;
+        s->raw_palette[(addr - 0xA00) >> 2] = val;
+        ftlcdc200_update_palette(s, n);
+        return;
+    }
+
+    switch (addr) {
+    case REG_FER:
+        s->fer = (uint32_t)val;
+        if (ftlcdc200_enabled(s)) {
+            qemu_console_resize(s->ds, s->cols, s->rows);
+        }
+        break;
+    case REG_PPR:
+        s->ppr = (uint32_t)val;
+        switch (s->ppr & PPR_RGB_MASK) {
+        case PPR_RGB1:
+            s->bpp = BPP_1;
+            break;
+        case PPR_RGB2:
+            s->bpp = BPP_2;
+            break;
+        case PPR_RGB4:
+            s->bpp = BPP_4;
+            break;
+        case PPR_RGB8:
+            s->bpp = BPP_8;
+            break;
+        case PPR_RGB12:
+            s->bpp = BPP_12;
+            break;
+        case PPR_RGB16_555:
+            s->bpp = BPP_16;
+            break;
+        case PPR_RGB16_565:
+            s->bpp = BPP_16_565;
+            break;
+        case PPR_RGB24:
+        default:
+            s->bpp = BPP_32;
+            break;
+        }
+        if (ftlcdc200_enabled(s)) {
+            qemu_console_resize(s->ds, s->cols, s->rows);
+        }
+        break;
+    case REG_IER:
+        s->ier = (uint32_t)val;
+        ftlcdc200_update_irq(s);
+        break;
+    case REG_ISCR:
+        s->isr &= ~((uint32_t)val);
+        ftlcdc200_update_irq(s);
+        break;
+    case REG_FB0:
+        s->fb[0] = (uint32_t)val;
+        break;
+    case REG_FB1:
+        s->fb[1] = (uint32_t)val;
+        break;
+    case REG_FB2:
+        s->fb[2] = (uint32_t)val;
+        break;
+    case REG_FB3:
+        s->fb[3] = (uint32_t)val;
+        break;
+    case REG_HT:
+        s->ht = (uint32_t)val;
+        n = ((s->ht & 0xff) + 1) << 4;
+        ftlcdc200_resize(s, n, s->rows);
+        break;
+    case REG_VT0:
+        s->vt0 = (uint32_t)val;
+        n = (s->vt0 & 0xfff) + 1;
+        ftlcdc200_resize(s, s->cols, n);
+        break;
+    case REG_VT1:
+        s->vt1 = (uint32_t)val;
+        break;
+    case REG_POL:
+        s->pol = (uint32_t)val;
+        break;
+    case REG_SPPR:
+        s->sppr = (uint32_t)val;
+        break;
+    default:
+        break;
+    }
+}
+
+static const MemoryRegionOps ftlcdc200_ops = {
+    .read  = ftlcdc200_mem_read,
+    .write = ftlcdc200_mem_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void ftlcdc200_reset(DeviceState *ds)
+{
+    SysBusDevice *busdev = SYS_BUS_DEVICE(ds);
+    Ftlcdc200State *s = FTLCDC200(FROM_SYSBUS(Ftlcdc200State, busdev));
+
+    s->fer   = 0;
+    s->ppr   = 0;
+    s->ier   = 0;
+    s->isr   = 0;
+    s->sppr  = 0;
+    s->fb[0] = 0;
+    s->fb[1] = 0;
+    s->fb[2] = 0;
+    s->fb[3] = 0;
+    s->ht    = 0;
+    s->vt0   = 0;
+    s->vt1   = 0;
+    s->pol   = 0;
+    s->cols  = 0;
+    s->rows  = 0;
+    s->bpp   = 0;
+    s->invalidate = 1;
+
+    /* Make sure we redraw, and at the right size */
+    ftlcdc200_invalidate_display(s);
+}
+
+static int ftlcdc200_init(SysBusDevice *dev)
+{
+    Ftlcdc200State *s = FTLCDC200(FROM_SYSBUS(Ftlcdc200State, dev));
+
+    memory_region_init_io(&s->iomem,
+                          &ftlcdc200_ops,
+                          s,
+                          TYPE_FTLCDC200,
+                          0x10000);
+    sysbus_init_mmio(dev, &s->iomem);
+    sysbus_init_irq(dev, &s->irq[IRQ_ALL]);
+    sysbus_init_irq(dev, &s->irq[IRQ_VSTATUS]);
+    sysbus_init_irq(dev, &s->irq[IRQ_BASEUPT]);
+    sysbus_init_irq(dev, &s->irq[IRQ_FIFOUR]);
+    sysbus_init_irq(dev, &s->irq[IRQ_BUSERR]);
+    s->ds = graphic_console_init(ftlcdc200_update_display,
+                                 ftlcdc200_invalidate_display,
+                                 NULL, NULL, s);
+    return 0;
+}
+
+static void ftlcdc200_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+    k->init     = ftlcdc200_init;
+    dc->reset   = ftlcdc200_reset;
+    dc->vmsd    = &vmstate_ftlcdc200;
+    dc->no_user = 1;
+}
+
+static const TypeInfo ftlcdc200_info = {
+    .name          = TYPE_FTLCDC200,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(Ftlcdc200State),
+    .class_init    = ftlcdc200_class_init,
+};
+
+static void ftlcdc200_register_types(void)
+{
+    type_register_static(&ftlcdc200_info);
+}
+
+type_init(ftlcdc200_register_types)
diff --git a/hw/arm/ftlcdc200.h b/hw/arm/ftlcdc200.h
new file mode 100644
index 0000000..46d0df8
--- /dev/null
+++ b/hw/arm/ftlcdc200.h
@@ -0,0 +1,110 @@
+/*
+ * Faraday FTLCDC200 Color LCD Controller
+ *
+ * Copyright (c) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com>
+ *
+ * This code is licensed under the GNU LGPL
+ */
+
+#ifndef HW_ARM_FTLCDC2XX_H
+#define HW_ARM_FTLCDC2XX_H
+
+/* HW Registers */
+
+#define REG_FER         0x00000
+#define REG_PPR         0x00004
+#define REG_IER         0x00008
+#define REG_ISCR        0x0000C
+#define REG_ISR         0x00010
+#define REG_FB0         0x00018
+#define REG_FB1         0x00024
+#define REG_FB2         0x00030
+#define REG_FB3         0x0003C
+
+#define REG_HT          0x00100
+#define REG_VT0         0x00104
+#define REG_VT1         0x00108
+#define REG_POL         0x0010C
+
+#define REG_SPPR        0x00200
+#define REG_CCIR        0x00204
+
+/* LCD Function Enable Register */
+#define FER_EN          (1 << 0)
+#define FER_ON          (1 << 1)
+#define FER_YUV420      (3 << 2)
+#define FER_YUV422      (2 << 2)
+#define FER_YUV         (1 << 3)
+
+/* LCD Panel Pixel Register */
+#define PPR_BPP_1       (0 << 0)
+#define PPR_BPP_2       (1 << 0)
+#define PPR_BPP_4       (2 << 0)
+#define PPR_BPP_8       (3 << 0)
+#define PPR_BPP_16      (4 << 0)
+#define PPR_BPP_24      (5 << 0)
+#define PPR_BPP_MASK    (7 << 0)
+#define PPR_PWROFF      (1 << 3)
+#define PPR_BGR         (1 << 4)
+#define PPR_ENDIAN_LBLP (0 << 5)
+#define PPR_ENDIAN_BBBP (1 << 5)
+#define PPR_ENDIAN_LBBP (2 << 5)
+#define PPR_ENDIAN_MASK (3 << 5)
+#define PPR_RGB1        (PPR_BPP_1)
+#define PPR_RGB2        (PPR_BPP_2)
+#define PPR_RGB4        (PPR_BPP_4)
+#define PPR_RGB8        (PPR_BPP_8)
+#define PPR_RGB12       (PPR_BPP_16 | (2 << 7))
+#define PPR_RGB16_555   (PPR_BPP_16 | (1 << 7))
+#define PPR_RGB16_565   (PPR_BPP_16 | (0 << 7))
+#define PPR_RGB24       (PPR_BPP_24)
+#define PPR_RGB32       (PPR_BPP_24)
+#define PPR_RGB_MASK    (PPR_BPP_MASK | (3 << 7))
+#define PPR_VCOMP_VSYNC (0 << 9)
+#define PPR_VCOMP_VBP   (1 << 9)
+#define PPR_VCOMP_VAIMG (2 << 9)
+#define PPR_VCOMP_VFP   (3 << 9)
+#define PPR_VCOMP_MASK  (3 << 9)
+
+/* LCD Interrupt Enable Register */
+#define IER_FIFOUR      (1 << 0)
+#define IER_NEXTFB      (1 << 1)
+#define IER_VCOMP       (1 << 2)
+#define IER_BUSERR      (1 << 3)
+
+/* LCD Interrupt Status Register */
+#define ISR_FIFOUR      (1 << 0)
+#define ISR_NEXTFB      (1 << 1)
+#define ISR_VCOMP       (1 << 2)
+#define ISR_BUSERR      (1 << 3)
+
+/* LCD Horizontal Timing Control Register */
+#define HT_HBP(x)       ((((x) - 1) & 0xff) << 24)
+#define HT_HFP(x)       ((((x) - 1) & 0xff) << 16)
+#define HT_HSYNC(x)     ((((x) - 1) & 0xff) << 8)
+#define HT_PL(x)        (((x >> 4) - 1) & 0xff)
+
+/* LCD Vertical Timing Control Register 0 */
+#define VT0_VFP(x)      (((x) & 0xff) << 24)
+#define VT0_VSYNC(x)    ((((x) - 1) & 0x3f) << 16)
+#define VT0_LF(x)       (((x) - 1) & 0xfff)
+
+/* LCD Polarity Control Register */
+#define POL_IVS         (1 << 0)
+#define POL_IHS         (1 << 1)
+#define POL_ICK         (1 << 2)
+#define POL_IDE         (1 << 3)
+#define POL_IPWR        (1 << 4)
+#define POL_DIV(x)      ((((x) - 1) & 0x7f) << 8)
+
+/* LCD Serial Panel Pixel Register */
+#define SPPR_SERIAL     (1 << 0)
+#define SPPR_DELTA      (1 << 1)
+#define SPPR_CS_RGB     (0 << 2)
+#define SPPR_CS_BRG     (1 << 2)
+#define SPPR_CS_GBR     (2 << 2)
+#define SPPR_LSR        (1 << 4)
+#define SPPR_AUO052     (1 << 5)
+
+#endif /* __FTLCDC2XX_H__ */
diff --git a/hw/arm/ftlcdc200_template.h b/hw/arm/ftlcdc200_template.h
new file mode 100644
index 0000000..f2785fc
--- /dev/null
+++ b/hw/arm/ftlcdc200_template.h
@@ -0,0 +1,439 @@
+/*
+ * Faraday FTLCDC200 Color LCD Controller
+ *
+ * Copyright (c) 2012 Faraday Technology
+ * Written by Dante Su <dantesu@faraday-tech.com>
+ *
+ * This code is licensed under the GNU LGPL
+ *
+ * Framebuffer format conversion routines.
+ */
+
+#ifndef ORDER
+
+#if BITS == 8
+#define COPY_PIXEL(to, from) \
+    do { *((to)++) = (from); } while (0)
+#elif BITS == 15 || BITS == 16
+#define COPY_PIXEL(to, from) \
+    do { \
+        *(uint16_t *)to = from;\
+        to += 2;\
+    } while (0)
+#elif BITS == 24
+#define COPY_PIXEL(to, from) \
+    do {\
+        *(to++) = from;\
+        *(to++) = (from) >> 8;\
+        *(to++) = (from) >> 16;\
+    } while (0)
+#elif BITS == 32
+#define COPY_PIXEL(to, from) \
+    do {\
+        *(uint32_t *)to = from;\
+        to += 4;\
+    } while (0)
+#else
+#error unknown bit depth
+#endif
+
+#undef RGB
+#define BORDER bgr
+#define ORDER 0
+#include "ftlcdc200_template.h"
+#define ORDER 1
+#include "ftlcdc200_template.h"
+#define ORDER 2
+#include "ftlcdc200_template.h"
+#undef BORDER
+#define RGB
+#define BORDER rgb
+#define ORDER 0
+#include "ftlcdc200_template.h"
+#define ORDER 1
+#include "ftlcdc200_template.h"
+#define ORDER 2
+#include "ftlcdc200_template.h"
+#undef BORDER
+
+static drawfn glue(ftlcdc200_draw_fn_, BITS)[48] = {
+    glue(ftlcdc200_draw_line1_lblp_bgr, BITS),
+    glue(ftlcdc200_draw_line2_lblp_bgr, BITS),
+    glue(ftlcdc200_draw_line4_lblp_bgr, BITS),
+    glue(ftlcdc200_draw_line8_lblp_bgr, BITS),
+    glue(ftlcdc200_draw_line16_555_lblp_bgr, BITS),
+    glue(ftlcdc200_draw_line32_lblp_bgr, BITS),
+    glue(ftlcdc200_draw_line16_lblp_bgr, BITS),
+    glue(ftlcdc200_draw_line12_lblp_bgr, BITS),
+
+    glue(ftlcdc200_draw_line1_bbbp_bgr, BITS),
+    glue(ftlcdc200_draw_line2_bbbp_bgr, BITS),
+    glue(ftlcdc200_draw_line4_bbbp_bgr, BITS),
+    glue(ftlcdc200_draw_line8_bbbp_bgr, BITS),
+    glue(ftlcdc200_draw_line16_555_bbbp_bgr, BITS),
+    glue(ftlcdc200_draw_line32_bbbp_bgr, BITS),
+    glue(ftlcdc200_draw_line16_bbbp_bgr, BITS),
+    glue(ftlcdc200_draw_line12_bbbp_bgr, BITS),
+
+    glue(ftlcdc200_draw_line1_lbbp_bgr, BITS),
+    glue(ftlcdc200_draw_line2_lbbp_bgr, BITS),
+    glue(ftlcdc200_draw_line4_lbbp_bgr, BITS),
+    glue(ftlcdc200_draw_line8_lbbp_bgr, BITS),
+    glue(ftlcdc200_draw_line16_555_lbbp_bgr, BITS),
+    glue(ftlcdc200_draw_line32_lbbp_bgr, BITS),
+    glue(ftlcdc200_draw_line16_lbbp_bgr, BITS),
+    glue(ftlcdc200_draw_line12_lbbp_bgr, BITS),
+
+    glue(ftlcdc200_draw_line1_lblp_rgb, BITS),
+    glue(ftlcdc200_draw_line2_lblp_rgb, BITS),
+    glue(ftlcdc200_draw_line4_lblp_rgb, BITS),
+    glue(ftlcdc200_draw_line8_lblp_rgb, BITS),
+    glue(ftlcdc200_draw_line16_555_lblp_rgb, BITS),
+    glue(ftlcdc200_draw_line32_lblp_rgb, BITS),
+    glue(ftlcdc200_draw_line16_lblp_rgb, BITS),
+    glue(ftlcdc200_draw_line12_lblp_rgb, BITS),
+
+    glue(ftlcdc200_draw_line1_bbbp_rgb, BITS),
+    glue(ftlcdc200_draw_line2_bbbp_rgb, BITS),
+    glue(ftlcdc200_draw_line4_bbbp_rgb, BITS),
+    glue(ftlcdc200_draw_line8_bbbp_rgb, BITS),
+    glue(ftlcdc200_draw_line16_555_bbbp_rgb, BITS),
+    glue(ftlcdc200_draw_line32_bbbp_rgb, BITS),
+    glue(ftlcdc200_draw_line16_bbbp_rgb, BITS),
+    glue(ftlcdc200_draw_line12_bbbp_rgb, BITS),
+
+    glue(ftlcdc200_draw_line1_lbbp_rgb, BITS),
+    glue(ftlcdc200_draw_line2_lbbp_rgb, BITS),
+    glue(ftlcdc200_draw_line4_lbbp_rgb, BITS),
+    glue(ftlcdc200_draw_line8_lbbp_rgb, BITS),
+    glue(ftlcdc200_draw_line16_555_lbbp_rgb, BITS),
+    glue(ftlcdc200_draw_line32_lbbp_rgb, BITS),
+    glue(ftlcdc200_draw_line16_lbbp_rgb, BITS),
+    glue(ftlcdc200_draw_line12_lbbp_rgb, BITS),
+};
+
+#undef BITS
+#undef COPY_PIXEL
+
+#else
+
+#if ORDER == 0
+#define NAME glue(glue(lblp_, BORDER), BITS)
+#ifdef HOST_WORDS_BIGENDIAN
+#define SWAP_WORDS 1
+#endif
+#elif ORDER == 1
+#define NAME glue(glue(bbbp_, BORDER), BITS)
+#ifndef HOST_WORDS_BIGENDIAN
+#define SWAP_WORDS 1
+#endif
+#else
+#define SWAP_PIXELS 1
+#define NAME glue(glue(lbbp_, BORDER), BITS)
+#ifdef HOST_WORDS_BIGENDIAN
+#define SWAP_WORDS 1
+#endif
+#endif
+
+#define FN_2(x, y) FN(x, y) FN(x + 1, y)
+#define FN_4(x, y) FN_2(x, y) FN_2(x + 2, y)
+#define FN_8(y) FN_4(0, y) FN_4(4, y)
+
+static void glue(ftlcdc200_draw_line1_, NAME)(void          *opaque,
+                                              uint8_t       *d,
+                                              const uint8_t *src,
+                                              int            width,
+                                              int            deststep)
+{
+    uint32_t *palette = opaque;
+    uint32_t data;
+    while (width > 0) {
+        data = *(uint32_t *)src;
+#ifdef SWAP_PIXELS
+#define FN(x, y) COPY_PIXEL(d, palette[(data >> (y + 7 - (x))) & 1]);
+#else
+#define FN(x, y) COPY_PIXEL(d, palette[(data >> ((x) + y)) & 1]);
+#endif
+#ifdef SWAP_WORDS
+        FN_8(24)
+        FN_8(16)
+        FN_8(8)
+        FN_8(0)
+#else
+        FN_8(0)
+        FN_8(8)
+        FN_8(16)
+        FN_8(24)
+#endif
+#undef FN
+        width -= 32;
+        src += 4;
+    }
+}
+
+static void glue(ftlcdc200_draw_line2_, NAME)(void          *opaque,
+                                              uint8_t       *d,
+                                              const uint8_t *src,
+                                              int            width,
+                                              int            deststep)
+{
+    uint32_t *palette = opaque;
+    uint32_t data;
+    while (width > 0) {
+        data = *(uint32_t *)src;
+#ifdef SWAP_PIXELS
+#define FN(x, y) COPY_PIXEL(d, palette[(data >> (y + 6 - (x) * 2)) & 3]);
+#else
+#define FN(x, y) COPY_PIXEL(d, palette[(data >> ((x) * 2 + y)) & 3]);
+#endif
+#ifdef SWAP_WORDS
+        FN_4(0, 24)
+        FN_4(0, 16)
+        FN_4(0, 8)
+        FN_4(0, 0)
+#else
+        FN_4(0, 0)
+        FN_4(0, 8)
+        FN_4(0, 16)
+        FN_4(0, 24)
+#endif
+#undef FN
+        width -= 16;
+        src += 4;
+    }
+}
+
+static void glue(ftlcdc200_draw_line4_, NAME)(void          *opaque,
+                                              uint8_t       *d,
+                                              const uint8_t *src,
+                                              int            width,
+                                              int            deststep)
+{
+    uint32_t *palette = opaque;
+    uint32_t data;
+    while (width > 0) {
+        data = *(uint32_t *)src;
+#ifdef SWAP_PIXELS
+#define FN(x, y) COPY_PIXEL(d, palette[(data >> (y + 4 - (x) * 4)) & 0xf]);
+#else
+#define FN(x, y) COPY_PIXEL(d, palette[(data >> ((x) * 4 + y)) & 0xf]);
+#endif
+#ifdef SWAP_WORDS
+        FN_2(0, 24)
+        FN_2(0, 16)
+        FN_2(0, 8)
+        FN_2(0, 0)
+#else
+        FN_2(0, 0)
+        FN_2(0, 8)
+        FN_2(0, 16)
+        FN_2(0, 24)
+#endif
+#undef FN
+        width -= 8;
+        src += 4;
+    }
+}
+
+static void glue(ftlcdc200_draw_line8_, NAME)(void          *opaque,
+                                              uint8_t       *d,
+                                              const uint8_t *src,
+                                              int            width,
+                                              int            deststep)
+{
+    uint32_t *palette = opaque;
+    uint32_t data;
+    while (width > 0) {
+        data = *(uint32_t *)src;
+#define FN(x) COPY_PIXEL(d, palette[(data >> (x)) & 0xff]);
+#ifdef SWAP_WORDS
+        FN(24)
+        FN(16)
+        FN(8)
+        FN(0)
+#else
+        FN(0)
+        FN(8)
+        FN(16)
+        FN(24)
+#endif
+#undef FN
+        width -= 4;
+        src += 4;
+    }
+}
+
+static void glue(ftlcdc200_draw_line16_, NAME)(void          *opaque,
+                                               uint8_t       *d,
+                                               const uint8_t *src,
+                                               int            width,
+                                               int            deststep)
+{
+    uint32_t data;
+    unsigned int r, g, b;
+    while (width > 0) {
+        data = *(uint32_t *)src;
+#ifdef SWAP_WORDS
+        data = bswap32(data);
+#endif
+#ifdef RGB
+#define LSB r
+#define MSB b
+#else
+#define LSB b
+#define MSB r
+#endif
+#if 0
+        LSB = data & 0x1f;
+        data >>= 5;
+        g = data & 0x3f;
+        data >>= 6;
+        MSB = data & 0x1f;
+        data >>= 5;
+#else
+        LSB = (data & 0x1f) << 3;
+        data >>= 5;
+        g = (data & 0x3f) << 2;
+        data >>= 6;
+        MSB = (data & 0x1f) << 3;
+        data >>= 5;
+#endif
+        COPY_PIXEL(d, glue(rgb_to_pixel, BITS)(r, g, b));
+        LSB = (data & 0x1f) << 3;
+        data >>= 5;
+        g = (data & 0x3f) << 2;
+        data >>= 6;
+        MSB = (data & 0x1f) << 3;
+        data >>= 5;
+        COPY_PIXEL(d, glue(rgb_to_pixel, BITS)(r, g, b));
+#undef MSB
+#undef LSB
+        width -= 2;
+        src += 4;
+    }
+}
+
+static void glue(ftlcdc200_draw_line32_, NAME)(void          *opaque,
+                                               uint8_t       *d,
+                                               const uint8_t *src,
+                                               int            width,
+                                               int            deststep)
+{
+    uint32_t data;
+    unsigned int r, g, b;
+    while (width > 0) {
+        data = *(uint32_t *)src;
+#ifdef RGB
+#define LSB r
+#define MSB b
+#else
+#define LSB b
+#define MSB r
+#endif
+#ifndef SWAP_WORDS
+        LSB = data & 0xff;
+        g = (data >> 8) & 0xff;
+        MSB = (data >> 16) & 0xff;
+#else
+        LSB = (data >> 24) & 0xff;
+        g = (data >> 16) & 0xff;
+        MSB = (data >> 8) & 0xff;
+#endif
+        COPY_PIXEL(d, glue(rgb_to_pixel, BITS)(r, g, b));
+#undef MSB
+#undef LSB
+        width--;
+        src += 4;
+    }
+}
+
+static void glue(ftlcdc200_draw_line16_555_, NAME)(void          *opaque,
+                                                   uint8_t       *d,
+                                                   const uint8_t *src,
+                                                   int            width,
+                                                   int            deststep)
+{
+    /* RGB 555 plus an intensity bit (which we ignore) */
+    uint32_t data;
+    unsigned int r, g, b;
+    while (width > 0) {
+        data = *(uint32_t *)src;
+#ifdef SWAP_WORDS
+        data = bswap32(data);
+#endif
+#ifdef RGB
+#define LSB r
+#define MSB b
+#else
+#define LSB b
+#define MSB r
+#endif
+        LSB = (data & 0x1f) << 3;
+        data >>= 5;
+        g = (data & 0x1f) << 3;
+        data >>= 5;
+        MSB = (data & 0x1f) << 3;
+        data >>= 5;
+        COPY_PIXEL(d, glue(rgb_to_pixel, BITS)(r, g, b));
+        LSB = (data & 0x1f) << 3;
+        data >>= 5;
+        g = (data & 0x1f) << 3;
+        data >>= 5;
+        MSB = (data & 0x1f) << 3;
+        data >>= 6;
+        COPY_PIXEL(d, glue(rgb_to_pixel, BITS)(r, g, b));
+#undef MSB
+#undef LSB
+        width -= 2;
+        src += 4;
+    }
+}
+
+static void glue(ftlcdc200_draw_line12_, NAME)(void          *opaque,
+                                               uint8_t       *d,
+                                               const uint8_t *src,
+                                               int            width,
+                                               int            deststep)
+{
+    /* RGB 444 with 4 bits of zeroes at the top of each halfword */
+    uint32_t data;
+    unsigned int r, g, b;
+    while (width > 0) {
+        data = *(uint32_t *)src;
+#ifdef SWAP_WORDS
+        data = bswap32(data);
+#endif
+#ifdef RGB
+#define LSB r
+#define MSB b
+#else
+#define LSB b
+#define MSB r
+#endif
+        LSB = (data & 0xf) << 4;
+        data >>= 4;
+        g = (data & 0xf) << 4;
+        data >>= 4;
+        MSB = (data & 0xf) << 4;
+        data >>= 8;
+        COPY_PIXEL(d, glue(rgb_to_pixel, BITS)(r, g, b));
+        LSB = (data & 0xf) << 4;
+        data >>= 4;
+        g = (data & 0xf) << 4;
+        data >>= 4;
+        MSB = (data & 0xf) << 4;
+        data >>= 8;
+        COPY_PIXEL(d, glue(rgb_to_pixel, BITS)(r, g, b));
+#undef MSB
+#undef LSB
+        width -= 2;
+        src += 4;
+    }
+}
+
+#undef SWAP_PIXELS
+#undef NAME
+#undef SWAP_WORDS
+#undef ORDER
+
+#endif
