Patchwork [08/18] hw: add QEMU model for Faraday LCD controller

login
register
mail settings
Submitter Dante
Date Jan. 18, 2013, 6:30 a.m.
Message ID <1358490651-18451-1-git-send-email-dantesu@faraday-tech.com>
Download mbox | patch
Permalink /patch/213487/
State New
Headers show

Comments

Dante - Jan. 18, 2013, 6:30 a.m.
Signed-off-by: Kuo-Jung Su <dantesu@faraday-tech.com>
---
 hw/ftlcdc200.c          |  494 +++++++++++++++++++++++++++++++++++++++++++++++
 hw/ftlcdc200.h          |  101 ++++++++++
 hw/ftlcdc200_template.h |  395 +++++++++++++++++++++++++++++++++++++
 3 files changed, 990 insertions(+)
 create mode 100644 hw/ftlcdc200.c
 create mode 100644 hw/ftlcdc200.h
 create mode 100644 hw/ftlcdc200_template.h

Patch

diff --git a/hw/ftlcdc200.c b/hw/ftlcdc200.c
new file mode 100644
index 0000000..a221051
--- /dev/null
+++ b/hw/ftlcdc200.c
@@ -0,0 +1,494 @@ 
+/*
+ * 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
+ */
+
+#include "sysbus.h"
+#include "ui/console.h"
+#include "framebuffer.h"
+#include "ui/pixel_ops.h"
+
+#include "ftlcdc200.h"
+
+enum ftlcdc200_irqpin {
+    IRQ_GLOBAL = 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,
+};
+
+typedef struct {
+    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 */
+    
+} ftlcdc200_state;
+
+static int vmstate_ftlcdc200_post_load(void *opaque, int version_id);
+
+static const VMStateDescription vmstate_ftlcdc200 = {
+    .name = "ftlcdc200",
+    .version_id = 2,
+    .minimum_version_id = 1,
+    .post_load = vmstate_ftlcdc200_post_load,
+    .fields = (VMStateField[]) {
+        VMSTATE_INT32(cols, ftlcdc200_state),
+        VMSTATE_INT32(rows, ftlcdc200_state),
+        VMSTATE_UINT32(bpp, ftlcdc200_state),
+        VMSTATE_INT32(invalidate, ftlcdc200_state),
+        VMSTATE_UINT32_ARRAY(palette, ftlcdc200_state, 256),
+        VMSTATE_UINT32_ARRAY(raw_palette, ftlcdc200_state, 128),
+        VMSTATE_UINT32(fer, ftlcdc200_state),
+        VMSTATE_UINT32(ppr, ftlcdc200_state),
+        VMSTATE_UINT32(ier, ftlcdc200_state),
+        VMSTATE_UINT32(isr, ftlcdc200_state),
+        VMSTATE_UINT32(sppr, ftlcdc200_state),
+        VMSTATE_UINT32_ARRAY(fb, ftlcdc200_state, 4),
+        VMSTATE_UINT32(ht, ftlcdc200_state),
+        VMSTATE_UINT32(vt0, ftlcdc200_state),
+        VMSTATE_UINT32(vt1, ftlcdc200_state),
+        VMSTATE_UINT32(pol, ftlcdc200_state),
+        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(ftlcdc200_state *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 inline void ftlcdc200_update_irq(ftlcdc200_state *s)
+{
+    uint32_t mask = s->ier & s->isr;
+    
+    if (mask) {
+        qemu_irq_raise(s->irq[IRQ_GLOBAL]);
+        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_GLOBAL]);
+        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)
+{
+    ftlcdc200_state *s = (ftlcdc200_state *)opaque;
+    drawfn* fntable;
+    drawfn fn;
+    int dest_width;
+    int src_width;
+    int bpp_offset;
+    int first;
+    int last;
+
+    if (!ftlcdc200_enabled(s))
+        return;
+        
+    //printf("[qemu] ftlcdc200_update_display: res=%dx%d, fb=0x%08x\n", s->cols, s->rows, s->fb[0]);
+
+    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:
+        fprintf(stderr, "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
+
+    //printf("[qemu] ftlcdc200_update_display: bpp=%d, ds=%d\n",
+    //    s->bpp, ds_get_bits_per_pixel(s->ds));
+
+    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)
+{
+    ftlcdc200_state *s = (ftlcdc200_state *)opaque;
+    s->invalidate = 1;
+    if (ftlcdc200_enabled(s)) {
+        qemu_console_resize(s->ds, s->cols, s->rows);
+    }
+}
+
+static void ftlcdc200_update_palette(ftlcdc200_state *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(ftlcdc200_state *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)
+{
+    ftlcdc200_state *s = (ftlcdc200_state *)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)
+{
+    ftlcdc200_state *s = (ftlcdc200_state *)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 int vmstate_ftlcdc200_post_load(void *opaque, int version_id)
+{
+    ftlcdc200_state *s = opaque;
+    /* Make sure we redraw, and at the right size */
+    ftlcdc200_invalidate_display(s);
+    return 0;
+}
+
+static int ftlcdc200_init(SysBusDevice *dev)
+{
+    ftlcdc200_state *s = FROM_SYSBUS(ftlcdc200_state, dev);
+    
+    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;
+
+    memory_region_init_io(&s->iomem, &ftlcdc200_ops, s, "ftlcdc200", 0x10000);
+    sysbus_init_mmio(dev, &s->iomem);
+    sysbus_init_irq(dev, &s->irq[IRQ_GLOBAL]);
+    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->no_user = 1;
+    dc->vmsd = &vmstate_ftlcdc200;
+}
+
+static TypeInfo ftlcdc200_info = {
+    .name          = "ftlcdc200",
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(ftlcdc200_state),
+    .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/ftlcdc200.h b/hw/ftlcdc200.h
new file mode 100644
index 0000000..772f5a7
--- /dev/null
+++ b/hw/ftlcdc200.h
@@ -0,0 +1,101 @@ 
+#ifndef __FTLCDC2XX_H__
+#define __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/ftlcdc200_template.h b/hw/ftlcdc200_template.h
new file mode 100644
index 0000000..24c34da
--- /dev/null
+++ b/hw/ftlcdc200_template.h
@@ -0,0 +1,395 @@ 
+/*
+ * 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) *(to++) = from
+#elif BITS == 15 || BITS == 16
+#define COPY_PIXEL(to, from) *(uint16_t *)to = from; to += 2;
+#elif BITS == 24
+#define COPY_PIXEL(to, from) \
+  *(to++) = from; *(to++) = (from) >> 8; *(to++) = (from) >> 16
+#elif BITS == 32
+#define COPY_PIXEL(to, from) *(uint32_t *)to = from; to += 4;
+#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