Patchwork [v5,19/24] hw/arm: add Faraday FTLCDC200 LCD controller support

login
register
mail settings
Submitter Kuo-Jung Su
Date Feb. 27, 2013, 7:15 a.m.
Message ID <1361949350-22241-20-git-send-email-dantesu@gmail.com>
Download mbox | patch
Permalink /patch/223539/
State New
Headers show

Comments

Kuo-Jung Su - Feb. 27, 2013, 7:15 a.m.
From: Kuo-Jung Su <dantesu@faraday-tech.com>

The FTLCDC200 Color LCD controller performs translation of
pixel-coded data into the required formats and timings to
drive a variety of single/dual mono and color LCDs.

Depending on the LCD type and mode, the unpacked data can represent:
   1. an actual true display gray or color value
   2. an address to a 256 x 16 bit wide palette RAM gray or color value.

The FTLCDC200 generates 4 individual interrupts for:
   1. DMA FIFO underflow
   2. base address update
   3. vertical status
   4. bus error.

There is also a single combined interrupt that is raised when any of
the individual interrupts become active.

Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
---
 hw/arm/Makefile.objs        |    1 +
 hw/arm/faraday_a369_soc.c   |   10 +
 hw/arm/ftlcdc200.c          |  513 +++++++++++++++++++++++++++++++++++++++++++
 hw/arm/ftlcdc200.h          |  110 ++++++++++
 hw/arm/ftlcdc200_template.h |  439 ++++++++++++++++++++++++++++++++++++
 5 files changed, 1073 insertions(+)
 create mode 100644 hw/arm/ftlcdc200.c
 create mode 100644 hw/arm/ftlcdc200.h
 create mode 100644 hw/arm/ftlcdc200_template.h

Patch

diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs
index 154e2ea..5630956 100644
--- a/hw/arm/Makefile.objs
+++ b/hw/arm/Makefile.objs
@@ -49,3 +49,4 @@  obj-y += ftnandc021.o
 obj-y += fti2c010.o
 obj-y += ftssp010.o
 obj-y += ftgmac100.o
+obj-y += ftlcdc200.o
diff --git a/hw/arm/faraday_a369_soc.c b/hw/arm/faraday_a369_soc.c
index d31049e..d39bf1d 100644
--- a/hw/arm/faraday_a369_soc.c
+++ b/hw/arm/faraday_a369_soc.c
@@ -252,6 +252,16 @@  a369soc_device_init(FaradaySoCState *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 int a369soc_init(SysBusDevice *busdev)
diff --git a/hw/arm/ftlcdc200.c b/hw/arm/ftlcdc200.c
new file mode 100644
index 0000000..76e761c
--- /dev/null
+++ b/hw/arm/ftlcdc200.c
@@ -0,0 +1,513 @@ 
+/*
+ * 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 "faraday.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 & ISR_FIFOUR) ? 1 : 0);
+        qemu_set_irq(s->irq[IRQ_BASEUPT], (mask & ISR_NEXTFB) ? 1 : 0);
+        qemu_set_irq(s->irq[IRQ_VSTATUS], (mask & ISR_VCOMP) ? 1 : 0);
+        qemu_set_irq(s->irq[IRQ_BUSERR],  (mask & ISR_BUSERR) ? 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);
+    }
+
+    bpp_offset = 0;
+    fn = fntable[s->bpp + bpp_offset];
+
+    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 = extract32(raw,  0, 5) << 3;
+        g = extract32(raw,  5, 5) << 3;
+        b = extract32(raw, 10, 5) << 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;
+    case 0xA00 ... 0xBFC:   /* palette.  */
+        return s->raw_palette[(addr - 0xA00) >> 2];
+    case REG_CCIR:
+    case 0x300 ... 0x310:   /* image parameters */
+    case 0x400 ... 0x40C:   /* color management */
+    case 0x600 ... 0x8FC:   /* gamma correction */
+    case 0xC00 ... 0xD3C:   /* cstn parameters */
+    case 0x1100 ... 0x112C: /* scalar control */
+    case 0x2000 ... 0xBFFC: /* osd control */
+        return 0;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "ftlcdc200: undefined memory access@0x%llx\n", addr);
+        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;
+
+    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;
+    case 0xA00 ... 0xBFC:   /* palette.  */
+        n = (addr - 0xA00) >> 2;
+        s->raw_palette[(addr - 0xA00) >> 2] = val;
+        ftlcdc200_update_palette(s, n);
+        break;
+    case 0x300 ... 0x310:   /* image parameters */
+    case 0x400 ... 0x40C:   /* color management */
+    case 0x600 ... 0x8FC:   /* gamma correction */
+    case 0xC00 ... 0xD3C:   /* cstn parameters */
+    case 0x1100 ... 0x112C: /* scalar control */
+    case 0x2000 ... 0xBFFC: /* osd control */
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "ftlcdc200: undefined memory access@0x%llx\n", addr);
+        break;
+    }
+}
+
+static const MemoryRegionOps mmio_ops = {
+    .read  = ftlcdc200_mem_read,
+    .write = ftlcdc200_mem_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    }
+};
+
+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;
+
+    memset(s->raw_palette, 0, sizeof(s->raw_palette));
+
+    /* 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,
+                          &mmio_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..53917e1
--- /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 /* LCD Function Enable Register */
+#define REG_PPR         0x00004 /* LCD Panel Pixel Register */
+#define REG_IER         0x00008 /* LCD Interrupt Enable Register */
+#define REG_ISCR        0x0000C /* LCD Interrupt Status Clear Register */
+#define REG_ISR         0x00010 /* LCD Interrupt Status Register */
+#define REG_FB0         0x00018 /* LCD Framebuffer Base Register 0 */
+#define REG_FB1         0x00024 /* LCD Framebuffer Base Register 1 */
+#define REG_FB2         0x00030 /* LCD Framebuffer Base Register 2 */
+#define REG_FB3         0x0003C /* LCD Framebuffer Base Register 3 */
+
+#define REG_HT          0x00100 /* LCD Horizontal Timing Control Register */
+#define REG_VT0         0x00104 /* LCD Vertical Timing Control Register 0 */
+#define REG_VT1         0x00108 /* LCD Vertical Timing Control Register 1 */
+#define REG_POL         0x0010C /* LCD Polarity Control Register */
+
+#define REG_SPPR        0x00200 /* LCD Serial Panel Pixel Register */
+#define REG_CCIR        0x00204 /* LCD CCIR565 Register */
+
+/* LCD Function Enable Register */
+#define FER_EN          (1 << 0)    /* chip enabled */
+#define FER_ON          (1 << 1)    /* screen on */
+#define FER_YUV420      (3 << 2)
+#define FER_YUV422      (2 << 2)
+#define FER_YUV         (1 << 3)    /* 1:YUV, 0:RGB */
+
+/* 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 /* HW_ARM_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