diff mbox

ARM HDLCD: Add HDLCD support to the ARM platforms that support it.

Message ID 9cc52dc513aa439174ddfaf9afbf5eabd1e4d170.1354808158.git.Liviu.Dudau@arm.com (mailing list archive)
State New, archived
Headers show

Commit Message

Liviu Dudau Dec. 6, 2012, 3:48 p.m. UTC
The ARM HDLCD device is now found in various new Versatile Express
coretiles as well as new Fast Models.

This driver adds support for the device tree bindings and supports
allocation of the framebuffer memory either through the reservation
mechanism that device tree support or through the use of CMA.
---
 Documentation/devicetree/bindings/fb/arm_hdlcd.txt |   30 +
 arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts        |    4 +
 arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts         |    1 +
 arch/arm/boot/dts/vexpress-v2p-ca5s.dts            |    4 +
 arch/arm/mach-vexpress/v2m.c                       |   51 ++
 drivers/video/Kconfig                              |   15 +
 drivers/video/Makefile                             |    1 +
 drivers/video/arm-hdlcd.c                          |  862 ++++++++++++++++++++
 include/linux/arm-hdlcd.h                          |  128 +++
 9 files changed, 1096 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/fb/arm_hdlcd.txt
 create mode 100644 drivers/video/arm-hdlcd.c
 create mode 100644 include/linux/arm-hdlcd.h
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/fb/arm_hdlcd.txt b/Documentation/devicetree/bindings/fb/arm_hdlcd.txt
new file mode 100644
index 0000000..d57f3af
--- /dev/null
+++ b/Documentation/devicetree/bindings/fb/arm_hdlcd.txt
@@ -0,0 +1,30 @@ 
+ARM HDLCD
+
+ARM HDLCD driver can use system memory for framebuffer that has been either
+reserved through the device tree /memreserve/ instruction or by using the
+Continuous Memory Allocator. The framebuffer memory needs to be contiguous
+and allocated in the first 4GB of physical space (for platforms that support
+LPAE).
+
+Required properties:
+- compatible: Should be "arm,hdlcd".
+- reg: Address and length of the register set.
+- interrupts: Should contain the interrupt used by the device.
+
+Optional properties:
+- mode: Should specify the initial video mode. Format as specified in
+  Documentation/fb/modedb.txt
+- framebuffer: Address and length of the physical memory allocated for buffer.
+
+
+Examples:
+
+	/memreserve/ 0xbf000000 0x01000000;
+
+        hdlcd@2b000000 {
+                compatible = "arm,hdlcd";
+                reg = <0 0x2b000000 0 0x1000>;
+                interrupts = <0 85 4>;
+                mode = "1024x768-16@60";
+                framebuffer = <0 0xff000000 0 0x01000000>;
+        };
diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts b/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts
index d12b34c..63c501e 100644
--- a/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts
+++ b/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts
@@ -9,6 +9,8 @@ 
 
 /dts-v1/;
 
+/memreserve/ 0xbf000000 0x01000000;
+
 / {
 	model = "V2P-CA15";
 	arm,hbi = <0x237>;
@@ -54,6 +56,8 @@ 
 		compatible = "arm,hdlcd";
 		reg = <0 0x2b000000 0 0x1000>;
 		interrupts = <0 85 4>;
+		mode = "1024x768-16@60";
+		framebuffer = <0 0xff000000 0 0x01000000>;
 	};
 
 	memory-controller@2b0a0000 {
diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts
index 4890a81..754ceff 100644
--- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts
+++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts
@@ -80,6 +80,7 @@ 
 		compatible = "arm,hdlcd";
 		reg = <0 0x2b000000 0 0x1000>;
 		interrupts = <0 85 4>;
+		mode = "1680x1050-32@60";
 	};
 
 	memory-controller@2b0a0000 {
diff --git a/arch/arm/boot/dts/vexpress-v2p-ca5s.dts b/arch/arm/boot/dts/vexpress-v2p-ca5s.dts
index 18917a0..dff0475 100644
--- a/arch/arm/boot/dts/vexpress-v2p-ca5s.dts
+++ b/arch/arm/boot/dts/vexpress-v2p-ca5s.dts
@@ -9,6 +9,8 @@ 
 
 /dts-v1/;
 
+/memreserve/ 0xbf000000 0x01000000;
+
 / {
 	model = "V2P-CA5s";
 	arm,hbi = <0x225>;
@@ -56,6 +58,8 @@ 
 		compatible = "arm,hdlcd";
 		reg = <0x2a110000 0x1000>;
 		interrupts = <0 85 4>;
+		mode = "640x480-16@60";
+		framebuffer = <0xbf000000 0x01000000>;
 	};
 
 	memory-controller@2a150000 {
diff --git a/arch/arm/mach-vexpress/v2m.c b/arch/arm/mach-vexpress/v2m.c
index 560e0df..bfe33b5 100644
--- a/arch/arm/mach-vexpress/v2m.c
+++ b/arch/arm/mach-vexpress/v2m.c
@@ -7,6 +7,7 @@ 
 #include <linux/io.h>
 #include <linux/smp.h>
 #include <linux/init.h>
+#include <linux/memblock.h>
 #include <linux/of_address.h>
 #include <linux/of_fdt.h>
 #include <linux/of_irq.h>
@@ -391,6 +392,14 @@  static struct v2m_osc v2m_mb_osc1 = {
 	.rate_default = 23750000,
 };
 
+static struct v2m_osc v2m_site_osc5 = {
+	.site = SYS_CFG_SITE_DB1,
+	.osc = 5,
+	.rate_min = 10000000,
+	.rate_max = 165000000,
+	.rate_default = 23750000,
+};
+
 static const char *v2m_ref_clk_periphs[] __initconst = {
 	"mb:wdt",   "1000f000.wdt",  "1c0f0000.wdt",	/* SP805 WDT */
 };
@@ -409,6 +418,10 @@  static const char *v2m_osc2_periphs[] __initconst = {
 	"mb:uart3", "1000c000.uart", "1c0c0000.uart",	/* PL011 UART3 */
 };
 
+static const char *v2m_osc5_periphs[] __initconst = {
+	"mb:hdlcd", "2b000000.hdlcd", "2b000000.hdlcd",	/* HDLCD */
+};
+
 static void __init v2m_clk_init(void)
 {
 	struct clk *clk;
@@ -436,6 +449,12 @@  static void __init v2m_clk_init(void)
 			CLK_IS_ROOT, 24000000);
 	for (i = 0; i < ARRAY_SIZE(v2m_osc2_periphs); i++)
 		WARN_ON(clk_register_clkdev(clk, NULL, v2m_osc2_periphs[i]));
+
+	/* Enable CoreTile OSC5 to be used by HDLCD */
+	v2m_site_osc5.site = v2m_get_master_site();
+	clk = v2m_osc_register("mb:osc5", &v2m_site_osc5);
+	for (i = 0; i < ARRAY_SIZE(v2m_osc5_periphs); i++)
+		WARN_ON(clk_register_clkdev(clk, NULL, v2m_osc5_periphs[i]));
 }
 
 static void __init v2m_timer_init(void)
@@ -542,6 +561,36 @@  MACHINE_START(VEXPRESS, "ARM-Versatile Express")
 	.restart	= v2m_restart,
 MACHINE_END
 
+static void __init v2m_dt_hdlcd_init(void)
+{
+#ifndef CONFIG_CMA
+	struct device_node *node;
+	int len, na, ns;
+	const __be32 *prop;
+	phys_addr_t fb_base, fb_size;
+
+	node = of_find_compatible_node(NULL, NULL, "arm,hdlcd");
+	if (!node)
+		return;
+
+	na = of_n_addr_cells(node);
+	ns = of_n_size_cells(node);
+
+	prop = of_get_property(node, "framebuffer", &len);
+	if (WARN_ON(!prop || len < (na + ns) * sizeof(*prop)))
+		return;
+
+	fb_base = of_read_number(prop, na);
+	fb_size = of_read_number(prop + na, ns);
+
+	if (WARN_ON(memblock_remove(fb_base, fb_size)))
+		return;
+#endif
+
+	v2m_cfg_write(SYS_CFG_MUXFPGA | SYS_CFG_SITE(SYS_CFG_SITE_MB),
+		v2m_get_master_site());
+};
+
 static struct map_desc v2m_rs1_io_desc __initdata = {
 	.virtual	= V2M_PERIPH,
 	.pfn		= __phys_to_pfn(0x1c000000),
@@ -599,6 +648,8 @@  void __init v2m_dt_init_early(void)
 			pr_warning("vexpress: DT HBI (%x) is not matching "
 					"hardware (%x)!\n", dt_hbi, hbi);
 	}
+
+	v2m_dt_hdlcd_init();
 }
 
 static  struct of_device_id vexpress_irq_match[] __initdata = {
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index d08d799..0cb7cb2 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -328,6 +328,21 @@  config FB_ARMCLCD
 	  here and read <file:Documentation/kbuild/modules.txt>.  The module
 	  will be called amba-clcd.
 
+config FB_ARMHDLCD
+	tristate "ARM High Definition LCD support"
+	depends on FB && ARM
+	select FB_CFB_FILLRECT
+	select FB_CFB_COPYAREA
+	select FB_CFB_IMAGEBLIT
+	help
+	  This framebuffer device driver is for the ARM High Definition
+	  Colour LCD controller.
+
+	  If you want to compile this as a module (=code which can be
+	  inserted into and removed from the running kernel), say M
+	  here and read <file:Documentation/kbuild/modules.txt>.  The module
+	  will be called arm-hdlcd.
+
 config FB_ACORN
 	bool "Acorn VIDC support"
 	depends on (FB = y) && ARM && ARCH_ACORN
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 23e948e..959ce1a 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -98,6 +98,7 @@  obj-$(CONFIG_FB_ATMEL)		  += atmel_lcdfb.o
 obj-$(CONFIG_FB_PVR2)             += pvr2fb.o
 obj-$(CONFIG_FB_VOODOO1)          += sstfb.o
 obj-$(CONFIG_FB_ARMCLCD)	  += amba-clcd.o
+obj-$(CONFIG_FB_ARMHDLCD)	  += arm-hdlcd.o
 obj-$(CONFIG_FB_68328)            += 68328fb.o
 obj-$(CONFIG_FB_GBE)              += gbefb.o
 obj-$(CONFIG_FB_CIRRUS)		  += cirrusfb.o
diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c
new file mode 100644
index 0000000..84fef29
--- /dev/null
+++ b/drivers/video/arm-hdlcd.c
@@ -0,0 +1,862 @@ 
+/*
+ * drivers/video/arm-hdlcd.c
+ *
+ * Copyright (C) 2012 ARM Limited
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file COPYING in the main directory of this archive
+ * for more details.
+ *
+ *  ARM HDLCD Controller
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <linux/mm.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/fb.h>
+#include <linux/clk.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/memblock.h>
+#include <linux/arm-hdlcd.h>
+#ifdef HDLCD_COUNT_BUFFERUNDERRUNS
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#endif
+
+#include "edid.h"
+
+#ifdef CONFIG_SERIAL_AMBA_PCU_UART
+int get_edid(u8 *msgbuf);
+#else
+#endif
+
+#define to_hdlcd_device(info)	container_of(info, struct hdlcd_device, fb)
+
+static struct of_device_id  hdlcd_of_matches[] = {
+	{ .compatible	= "arm,hdlcd" },
+	{},
+};
+
+/* Framebuffer size. */
+static unsigned long framebuffer_size = SZ_8M;
+
+/* declare prototype function */
+static int hdlcd_pan_display(struct fb_var_screeninfo *var, struct fb_info *info);
+
+#ifdef HDLCD_COUNT_BUFFERUNDERRUNS
+static unsigned long buffer_underrun_events;
+static DEFINE_SPINLOCK(hdlcd_underrun_lock);
+
+static void hdlcd_underrun_set(unsigned long val)
+{
+	spin_lock(&hdlcd_underrun_lock);
+	buffer_underrun_events = val;
+	spin_unlock(&hdlcd_underrun_lock);
+}
+
+static unsigned long hdlcd_underrun_get(void)
+{
+	unsigned long val;
+	spin_lock(&hdlcd_underrun_lock);
+	val = buffer_underrun_events;
+	spin_unlock(&hdlcd_underrun_lock);
+	return val;
+}
+
+#ifdef CONFIG_PROC_FS
+static int hdlcd_underrun_show(struct seq_file *m, void *v)
+{
+	unsigned char underrun_string[32];
+	snprintf(underrun_string, 32, "%lu\n", hdlcd_underrun_get());
+	seq_puts(m, underrun_string);
+	return 0;
+}
+
+static int proc_hdlcd_underrun_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, hdlcd_underrun_show, NULL);
+}
+
+static const struct file_operations proc_hdlcd_underrun_operations = {
+	.open		= proc_hdlcd_underrun_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int hdlcd_underrun_init(void)
+{
+	hdlcd_underrun_set(0);
+	proc_create("hdlcd_underrun", 0, NULL, &proc_hdlcd_underrun_operations);
+	return 0;
+}
+static void hdlcd_underrun_close(void)
+{
+	remove_proc_entry("hdlcd_underrun", NULL);
+}
+#else
+static int hdlcd_underrun_init(void) { return 0; }
+static void hdlcd_underrun_close(void) { }
+#endif  /* CONFIG_PROC_FS */
+#endif  /* HDLCD_COUNT_BUFFERUNDERRUNS */
+
+static char *fb_mode = "1680x1050-32@60\0\0\0\0\0";
+
+static struct fb_var_screeninfo cached_var_screeninfo;
+
+static struct fb_videomode __devinitdata hdlcd_default_mode = {
+	.refresh	= 60,
+	.xres		= 1680,
+	.yres		= 1050,
+	.pixclock	= 8403,
+	.left_margin	= 80,
+	.right_margin	= 48,
+	.upper_margin	= 21,
+	.lower_margin	= 3,
+	.hsync_len	= 32,
+	.vsync_len	= 6,
+	.sync		= FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
+	.vmode		= FB_VMODE_NONINTERLACED
+};
+
+static inline void hdlcd_enable(struct hdlcd_device *hdlcd)
+{
+	dev_dbg(hdlcd->dev, "HDLCD: output enabled\n");
+	writel(1, hdlcd->base + HDLCD_REG_COMMAND);
+}
+
+static inline void hdlcd_disable(struct hdlcd_device *hdlcd)
+{
+	dev_dbg(hdlcd->dev, "HDLCD: output disabled\n");
+	writel(0, hdlcd->base + HDLCD_REG_COMMAND);
+}
+
+static int hdlcd_set_bitfields(struct hdlcd_device *hdlcd,
+				struct fb_var_screeninfo *var)
+{
+	int ret = 0;
+
+	memset(&var->transp, 0, sizeof(var->transp));
+	var->red.msb_right = 0;
+	var->green.msb_right = 0;
+	var->blue.msb_right = 0;
+	var->blue.offset = 0;
+
+	switch (var->bits_per_pixel) {
+	case 8:
+		/* pseudocolor */
+		var->red.length = 8;
+		var->green.length = 8;
+		var->blue.length = 8;
+		break;
+	case 16:
+		/* 565 format */
+		var->red.length = 5;
+		var->green.length = 6;
+		var->blue.length = 5;
+		break;
+	case 32:
+		var->transp.length = 8;
+	case 24:
+		var->red.length = 8;
+		var->green.length = 8;
+		var->blue.length = 8;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	if (!ret) {
+		var->green.offset = var->blue.length;
+		var->red.offset = var->green.offset + var->green.length;
+	}
+
+	return ret;
+}
+
+static int hdlcd_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+	struct hdlcd_device *hdlcd = to_hdlcd_device(info);
+	int bpp = var->bits_per_pixel / 8;
+
+#ifdef HDLCD_NO_VIRTUAL_SCREEN
+	var->yres_virtual = var->yres;
+#else
+	var->yres_virtual = 2 * var->yres;
+#endif
+
+	if ((var->xres_virtual * bpp * var->yres_virtual) > hdlcd->fb.fix.smem_len)
+		return -ENOMEM;
+
+	if (var->xres > HDLCD_MAX_XRES || var->yres > HDLCD_MAX_YRES)
+		return -EINVAL;
+
+	/* make sure the bitfields are set appropriately */
+	return hdlcd_set_bitfields(hdlcd, var);
+}
+
+#define WRITE_HDLCD_REG(reg, value)	writel((value), hdlcd->base + (reg))
+#define READ_HDLCD_REG(reg)		readl(hdlcd->base + (reg))
+
+static int hdlcd_set_par(struct fb_info *info)
+{
+	struct hdlcd_device *hdlcd = to_hdlcd_device(info);
+	int bpp = hdlcd->fb.var.bits_per_pixel / 8;
+	int polarities;
+	int old_yoffset;
+
+	/* check for shortcuts */
+	old_yoffset = cached_var_screeninfo.yoffset;
+	cached_var_screeninfo.yoffset = info->var.yoffset;
+	if (!memcmp(&info->var, &cached_var_screeninfo,
+			sizeof(struct fb_var_screeninfo))) {
+		if(old_yoffset != info->var.yoffset) {
+			hdlcd_pan_display(&info->var, info);
+		}
+		/* or no change */
+		return 0;
+	}
+
+	hdlcd->fb.fix.line_length = hdlcd->fb.var.xres * bpp;
+
+	if (hdlcd->fb.var.bits_per_pixel >= 16)
+		hdlcd->fb.fix.visual = FB_VISUAL_TRUECOLOR;
+	else
+		hdlcd->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR;
+
+	memcpy(&cached_var_screeninfo, &info->var,
+				sizeof(struct fb_var_screeninfo));
+
+	polarities = HDLCD_POLARITY_DATAEN | HDLCD_POLARITY_PIXELCLK | HDLCD_POLARITY_DATA;
+	polarities |= (hdlcd->fb.var.sync & FB_SYNC_HOR_HIGH_ACT) ? HDLCD_POLARITY_HSYNC : 0;
+	polarities |= (hdlcd->fb.var.sync & FB_SYNC_VERT_HIGH_ACT) ? HDLCD_POLARITY_VSYNC : 0;
+
+	hdlcd_disable(hdlcd);
+
+	WRITE_HDLCD_REG(HDLCD_REG_FB_LINE_LENGTH, hdlcd->fb.var.xres * bpp);
+	WRITE_HDLCD_REG(HDLCD_REG_FB_LINE_PITCH, hdlcd->fb.var.xres * bpp);
+	WRITE_HDLCD_REG(HDLCD_REG_FB_LINE_COUNT, hdlcd->fb.var.yres - 1);
+	WRITE_HDLCD_REG(HDLCD_REG_V_SYNC, hdlcd->fb.var.vsync_len - 1);
+	WRITE_HDLCD_REG(HDLCD_REG_V_BACK_PORCH, hdlcd->fb.var.upper_margin - 1);
+	WRITE_HDLCD_REG(HDLCD_REG_V_DATA, hdlcd->fb.var.yres - 1);
+	WRITE_HDLCD_REG(HDLCD_REG_V_FRONT_PORCH, hdlcd->fb.var.lower_margin - 1);
+	WRITE_HDLCD_REG(HDLCD_REG_H_SYNC, hdlcd->fb.var.hsync_len - 1);
+	WRITE_HDLCD_REG(HDLCD_REG_H_BACK_PORCH, hdlcd->fb.var.left_margin - 1);
+	WRITE_HDLCD_REG(HDLCD_REG_H_DATA, hdlcd->fb.var.xres - 1);
+	WRITE_HDLCD_REG(HDLCD_REG_H_FRONT_PORCH, hdlcd->fb.var.right_margin - 1);
+	WRITE_HDLCD_REG(HDLCD_REG_POLARITIES, polarities);
+	WRITE_HDLCD_REG(HDLCD_REG_PIXEL_FORMAT, (bpp - 1) << 3);
+
+	/*
+	 * The format of the HDLCD_REG_<color>_SELECT register is:
+	 *   - bits[23:16] - default value for that color component
+	 *   - bits[11:8]  - number of bits to extract for each color component
+	 *   - bits[4:0]   - index of the lowest bit to extract
+	 *
+	 * The default color value is used when bits[11:8] read zero, when the
+	 * pixel is outside the visible frame area or when there is a
+	 * buffer underrun.
+	 */
+	WRITE_HDLCD_REG(HDLCD_REG_RED_SELECT, hdlcd->fb.var.red.offset | \
+				((hdlcd->fb.var.red.length & 0xf) << 8));
+	WRITE_HDLCD_REG(HDLCD_REG_GREEN_SELECT, hdlcd->fb.var.green.offset | \
+				((hdlcd->fb.var.green.length & 0xf) << 8));
+	WRITE_HDLCD_REG(HDLCD_REG_BLUE_SELECT, hdlcd->fb.var.blue.offset | \
+				((hdlcd->fb.var.blue.length & 0xf) << 8));
+
+#ifdef HDLCD_SHOW_UNDERRUN
+	WRITE_HDLCD_REG(HDLCD_REG_RED_SELECT, (0x00ff0000 |
+				READ_HDLCD_REG(HDLCD_REG_RED_SELECT)));
+#endif
+
+	clk_prepare(hdlcd->clk);
+	clk_set_rate(hdlcd->clk, (1000000000 / hdlcd->fb.var.pixclock) * 1000);
+	clk_enable(hdlcd->clk);
+
+	hdlcd_enable(hdlcd);
+
+	return 0;
+}
+
+static int hdlcd_setcolreg(unsigned int regno, unsigned int red, unsigned int green,
+		unsigned int blue, unsigned int transp, struct fb_info *info)
+{
+	if (regno < 16) {
+		u32 *pal = info->pseudo_palette;
+
+		pal[regno] = ((red >> 8) << info->var.red.offset) |
+			((green >> 8) << info->var.green.offset) |
+			((blue >> 8) << info->var.blue.offset);
+	}
+
+	return 0;
+}
+
+static irqreturn_t hdlcd_irq(int irq, void *data)
+{
+	struct hdlcd_device *hdlcd = data;
+	unsigned long irq_mask, irq_status;
+
+	irq_mask = READ_HDLCD_REG(HDLCD_REG_INT_MASK);
+	irq_status = READ_HDLCD_REG(HDLCD_REG_INT_STATUS);
+
+	/* acknowledge interrupt(s) */
+	WRITE_HDLCD_REG(HDLCD_REG_INT_CLEAR, irq_status);
+#ifdef HDLCD_COUNT_BUFFERUNDERRUNS
+	if (irq_status & HDLCD_INTERRUPT_UNDERRUN) {
+		/* increment the count */
+		hdlcd_underrun_set(hdlcd_underrun_get() + 1);
+	}
+#endif
+	if (irq_status & HDLCD_INTERRUPT_VSYNC) {
+		/* disable future VSYNC interrupts */
+		WRITE_HDLCD_REG(HDLCD_REG_INT_MASK, irq_mask & ~HDLCD_INTERRUPT_VSYNC);
+
+		complete(&hdlcd->vsync_completion);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int hdlcd_wait_for_vsync(struct fb_info *info)
+{
+	struct hdlcd_device *hdlcd = to_hdlcd_device(info);
+	unsigned long irq_mask;
+	int err;
+
+	/* enable VSYNC interrupt */
+	irq_mask = READ_HDLCD_REG(HDLCD_REG_INT_MASK);
+	WRITE_HDLCD_REG(HDLCD_REG_INT_MASK, irq_mask | HDLCD_INTERRUPT_VSYNC);
+
+	err = wait_for_completion_interruptible_timeout(&hdlcd->vsync_completion,
+							msecs_to_jiffies(100));
+
+	if (!err)
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+static int hdlcd_blank(int blank_mode, struct fb_info *info)
+{
+	struct hdlcd_device *hdlcd = to_hdlcd_device(info);
+
+	switch (blank_mode) {
+	case FB_BLANK_POWERDOWN:
+		clk_disable(hdlcd->clk);
+	case FB_BLANK_NORMAL:
+		hdlcd_disable(hdlcd);
+		break;
+	case FB_BLANK_UNBLANK:
+		clk_enable(hdlcd->clk);
+		hdlcd_enable(hdlcd);
+		break;
+	case FB_BLANK_VSYNC_SUSPEND:
+	case FB_BLANK_HSYNC_SUSPEND:
+	default:
+		return 1;
+	}
+
+	return 0;
+}
+
+static void hdlcd_mmap_open(struct vm_area_struct *vma)
+{
+}
+
+static void hdlcd_mmap_close(struct vm_area_struct *vma)
+{
+}
+
+static struct vm_operations_struct hdlcd_mmap_ops = {
+	.open	= hdlcd_mmap_open,
+	.close	= hdlcd_mmap_close,
+};
+
+static int hdlcd_mmap(struct fb_info *info, struct vm_area_struct *vma)
+{
+	struct hdlcd_device *hdlcd = to_hdlcd_device(info);
+	unsigned long off;
+	unsigned long start;
+	unsigned long len = hdlcd->fb.fix.smem_len;
+
+	if (vma->vm_end - vma->vm_start == 0)
+		return 0;
+	if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
+		return -EINVAL;
+
+	off = vma->vm_pgoff << PAGE_SHIFT;
+	if ((off >= len) || (vma->vm_end - vma->vm_start + off) > len)
+		return -EINVAL;
+
+	start = hdlcd->fb.fix.smem_start;
+	off += start;
+
+	vma->vm_pgoff = off >> PAGE_SHIFT;
+	vma->vm_flags |= VM_IO;
+	vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
+	vma->vm_ops = &hdlcd_mmap_ops;
+	if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
+				vma->vm_end - vma->vm_start,
+				vma->vm_page_prot))
+		return -EAGAIN;
+
+	return 0;
+}
+
+static int hdlcd_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+	struct hdlcd_device *hdlcd = to_hdlcd_device(info);
+
+	hdlcd->fb.var.yoffset = var->yoffset;
+	WRITE_HDLCD_REG(HDLCD_REG_FB_BASE, hdlcd->fb.fix.smem_start +
+			(var->yoffset * hdlcd->fb.fix.line_length));
+
+	hdlcd_wait_for_vsync(info);
+
+	return 0;
+}
+
+static int hdlcd_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
+{
+	int err;
+
+	switch (cmd) {
+	case FBIO_WAITFORVSYNC:
+		err = hdlcd_wait_for_vsync(info);
+		break;
+	default:
+		err = -ENOIOCTLCMD;
+		break;
+	}
+
+	return err;
+}
+
+static struct fb_ops hdlcd_ops = {
+	.owner			= THIS_MODULE,
+	.fb_check_var		= hdlcd_check_var,
+	.fb_set_par		= hdlcd_set_par,
+	.fb_setcolreg		= hdlcd_setcolreg,
+	.fb_blank		= hdlcd_blank,
+	.fb_fillrect		= cfb_fillrect,
+	.fb_copyarea		= cfb_copyarea,
+	.fb_imageblit		= cfb_imageblit,
+	.fb_mmap		= hdlcd_mmap,
+	.fb_pan_display		= hdlcd_pan_display,
+	.fb_ioctl		= hdlcd_ioctl,
+	.fb_compat_ioctl	= hdlcd_ioctl
+};
+
+static int hdlcd_setup(struct hdlcd_device *hdlcd)
+{
+	u32 version;
+	int err = -EFAULT;
+
+	hdlcd->fb.device = hdlcd->dev;
+
+	hdlcd->clk = clk_get(hdlcd->dev, NULL);
+	if (IS_ERR(hdlcd->clk)) {
+		dev_err(hdlcd->dev, "HDLCD: unable to find clock data\n");
+		return PTR_ERR(hdlcd->clk);
+	}
+
+	hdlcd->base = ioremap_nocache(hdlcd->fb.fix.mmio_start, hdlcd->fb.fix.mmio_len);
+	if (!hdlcd->base) {
+		dev_err(hdlcd->dev, "HDLCD: unable to map registers\n");
+		goto remap_err;
+	}
+
+	hdlcd->fb.pseudo_palette = kmalloc(sizeof(u32) * 16, GFP_KERNEL);
+	if (!hdlcd->fb.pseudo_palette) {
+		dev_err(hdlcd->dev, "HDLCD: unable to allocate pseudo_palette memory\n");
+		err = -ENOMEM;
+		goto kmalloc_err;
+	}
+
+	version = readl(hdlcd->base + HDLCD_REG_VERSION);
+	if ((version & HDLCD_PRODUCT_MASK) != HDLCD_PRODUCT_ID) {
+		dev_err(hdlcd->dev, "HDLCD: unknown product id: 0x%x\n", version);
+		err = -EINVAL;
+		goto kmalloc_err;
+	}
+	dev_info(hdlcd->dev, "HDLCD: found ARM HDLCD version r%dp%d\n",
+		(version & HDLCD_VERSION_MAJOR_MASK) >> 8,
+		version & HDLCD_VERSION_MINOR_MASK);
+
+	strcpy(hdlcd->fb.fix.id, "hdlcd");
+	hdlcd->fb.fbops			= &hdlcd_ops;
+	hdlcd->fb.flags			= FBINFO_FLAG_DEFAULT/* | FBINFO_VIRTFB*/;
+
+	hdlcd->fb.fix.type		= FB_TYPE_PACKED_PIXELS;
+	hdlcd->fb.fix.type_aux		= 0;
+	hdlcd->fb.fix.xpanstep		= 0;
+	hdlcd->fb.fix.ypanstep		= 1;
+	hdlcd->fb.fix.ywrapstep		= 0;
+	hdlcd->fb.fix.accel		= FB_ACCEL_NONE;
+
+	hdlcd->fb.var.nonstd		= 0;
+	hdlcd->fb.var.activate		= FB_ACTIVATE_NOW;
+	hdlcd->fb.var.height		= -1;
+	hdlcd->fb.var.width		= -1;
+	hdlcd->fb.var.accel_flags	= 0;
+
+	init_completion(&hdlcd->vsync_completion);
+
+	if (hdlcd->edid) {
+		/* build modedb from EDID */
+		fb_edid_to_monspecs(hdlcd->edid, &hdlcd->fb.monspecs);
+		fb_videomode_to_modelist(hdlcd->fb.monspecs.modedb,
+					hdlcd->fb.monspecs.modedb_len,
+					&hdlcd->fb.modelist);
+		fb_find_mode(&hdlcd->fb.var, &hdlcd->fb, fb_mode,
+			hdlcd->fb.monspecs.modedb,
+			hdlcd->fb.monspecs.modedb_len,
+			&hdlcd_default_mode, 32);
+	} else {
+		hdlcd->fb.monspecs.hfmin	= 0;
+		hdlcd->fb.monspecs.hfmax	= 100000;
+		hdlcd->fb.monspecs.vfmin	= 0;
+		hdlcd->fb.monspecs.vfmax	= 400;
+		hdlcd->fb.monspecs.dclkmin	= 1000000;
+		hdlcd->fb.monspecs.dclkmax	= 100000000;
+		fb_find_mode(&hdlcd->fb.var, &hdlcd->fb, fb_mode, NULL, 0, &hdlcd_default_mode, 32);
+	}
+
+	dev_info(hdlcd->dev, "using %dx%d-%d@%d mode\n", hdlcd->fb.var.xres,
+		hdlcd->fb.var.yres, hdlcd->fb.var.bits_per_pixel,
+		hdlcd->fb.mode ? hdlcd->fb.mode->refresh : 60);
+	hdlcd->fb.var.xres_virtual	= hdlcd->fb.var.xres;
+#ifdef HDLCD_NO_VIRTUAL_SCREEN
+	hdlcd->fb.var.yres_virtual	= hdlcd->fb.var.yres;
+#else
+	hdlcd->fb.var.yres_virtual	= hdlcd->fb.var.yres * 2;
+#endif
+
+	/* initialise and set the palette */
+	if (fb_alloc_cmap(&hdlcd->fb.cmap, NR_PALETTE, 0)) {
+		dev_err(hdlcd->dev, "failed to allocate cmap memory\n");
+		err = -ENOMEM;
+		goto setup_err;
+	}
+	fb_set_cmap(&hdlcd->fb.cmap, &hdlcd->fb);
+
+	/* Allow max number of outstanding requests with the largest beat burst */
+	WRITE_HDLCD_REG(HDLCD_REG_BUS_OPTIONS, HDLCD_BUS_MAX_OUTSTAND | HDLCD_BUS_BURST_16);
+	/* Set the framebuffer base to start of allocated memory */
+	WRITE_HDLCD_REG(HDLCD_REG_FB_BASE, hdlcd->fb.fix.smem_start);
+#ifdef HDLCD_COUNT_BUFFERUNDERRUNS
+	/* turn on underrun interrupt for counting */
+	WRITE_HDLCD_REG(HDLCD_REG_INT_MASK, HDLCD_INTERRUPT_UNDERRUN);
+#else
+	/* Ensure interrupts are disabled */
+	WRITE_HDLCD_REG(HDLCD_REG_INT_MASK, 0);
+#endif
+	if (!register_framebuffer(&hdlcd->fb)) {
+		fb_set_var(&hdlcd->fb, &hdlcd->fb.var);
+		clk_enable(hdlcd->clk);
+		return 0;
+	}
+
+	dev_err(hdlcd->dev, "HDLCD: cannot register framebuffer\n");
+
+	fb_dealloc_cmap(&hdlcd->fb.cmap);
+setup_err:
+	iounmap(hdlcd->base);
+kmalloc_err:
+	kfree(hdlcd->fb.pseudo_palette);
+remap_err:
+	clk_put(hdlcd->clk);
+	return err;
+}
+
+static inline unsigned char atohex(u8 data)
+{
+	if (!isxdigit(data))
+		return 0;
+	/* truncate the upper nibble and add 9 to non-digit values */
+	return (data > 0x39) ? ((data & 0xf) + 9) : (data & 0xf);
+}
+
+/* EDID data is passed from devicetree in a literal string that can contain spaces and
+   the hexadecimal dump of the data */
+static int parse_edid_data(struct hdlcd_device *hdlcd, const u8 *edid_data, int data_len)
+{
+	int i, j;
+
+	if (!edid_data)
+		return -EINVAL;
+
+	hdlcd->edid = kzalloc(EDID_LENGTH, GFP_KERNEL);
+	if (!hdlcd->edid)
+		return -ENOMEM;
+
+	for (i = 0, j = 0; i < data_len; i++) {
+		if (isspace(edid_data[i]))
+			continue;
+		hdlcd->edid[j++] = atohex(edid_data[i]);
+		if (j >= EDID_LENGTH)
+			break;
+	}
+
+	if (j < EDID_LENGTH) {
+		kfree(hdlcd->edid);
+		hdlcd->edid = NULL;
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int __devinit hdlcd_probe(struct platform_device *pdev)
+{
+	int err = 0, i;
+	struct hdlcd_device *hdlcd;
+	struct resource *mem;
+#ifdef CONFIG_OF
+	struct device_node *of_node;
+#endif
+
+	memset(&cached_var_screeninfo, 0, sizeof(struct fb_var_screeninfo));
+
+	dev_dbg(&pdev->dev, "HDLCD: probing\n");
+
+	hdlcd = kzalloc(sizeof(*hdlcd), GFP_KERNEL);
+	if (!hdlcd)
+		return -ENOMEM;
+
+#ifdef CONFIG_OF
+	of_node = pdev->dev.of_node;
+	if (of_node) {
+		int len;
+		const u8 *edid;
+		const __be32 *prop = of_get_property(of_node, "mode", &len);
+		if (prop)
+			strncpy(fb_mode, (char *)prop, len);
+		prop = of_get_property(of_node, "framebuffer", &len);
+		if (prop) {
+			hdlcd->fb.fix.smem_start = of_read_ulong(prop,
+					of_n_addr_cells(of_node));
+			prop += of_n_addr_cells(of_node);
+			framebuffer_size = of_read_ulong(prop,
+					of_n_size_cells(of_node));
+			if (framebuffer_size > HDLCD_MAX_FRAMEBUFFER_SIZE)
+				framebuffer_size = HDLCD_MAX_FRAMEBUFFER_SIZE;
+			dev_dbg(&pdev->dev, "HDLCD: phys_addr = 0x%lx, size = 0x%lx\n",
+				hdlcd->fb.fix.smem_start, framebuffer_size);
+#ifdef CONFIG_CMA
+		} else {
+			/* allocate framebuffer memory using CMA */
+			dma_addr_t dma_addr;
+			void *virt_addr;
+
+			virt_addr = dma_alloc_writecombine(&pdev->dev,
+						framebuffer_size,
+						&dma_addr, GFP_KERNEL);
+			if (!virt_addr) {
+				dev_err(&pdev->dev, "HDLCD: failed to allocate"
+					" framebuffer memory!\n");
+				return -ENOMEM;
+			}
+			hdlcd->fb.fix.smem_start = dma_addr;
+			hdlcd->fb.screen_base = virt_addr;
+			dev_dbg(&pdev->dev, "HDLCD: phys_addr = 0x%lx, size = 0x%lx\n",
+				hdlcd->fb.fix.smem_start, framebuffer_size);
+#endif
+		}
+		edid = of_get_property(of_node, "edid", &len);
+		if (edid) {
+			err = parse_edid_data(hdlcd, edid, len);
+#ifdef CONFIG_SERIAL_AMBA_PCU_UART
+		} else {
+			/* ask the firmware to fetch the EDID */
+			dev_dbg(&pdev->dev, "HDLCD: Requesting EDID data\n");
+			hdlcd->edid = kzalloc(EDID_LENGTH, GFP_KERNEL);
+			if (!hdlcd->edid)
+				return -ENOMEM;
+			err = get_edid(hdlcd->edid);
+#endif /* CONFIG_SERIAL_AMBA_PCU_UART */
+		}
+		if (err)
+			dev_info(&pdev->dev, "HDLCD: Failed to parse EDID data\n");
+	}
+#endif /* CONFIG_OF */
+
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!mem) {
+		dev_err(&pdev->dev, "HDLCD: cannot get platform resources\n");
+		err = -EINVAL;
+		goto resource_err;
+	}
+
+	i = platform_get_irq(pdev, 0);
+	if (i < 0) {
+		dev_err(&pdev->dev, "HDLCD: no irq defined for vsync\n");
+		err = -ENOENT;
+		goto resource_err;
+	} else {
+		err = request_irq(i, hdlcd_irq, 0, dev_name(&pdev->dev), hdlcd);
+		if (err) {
+			dev_err(&pdev->dev, "HDLCD: unable to request irq\n");
+			goto resource_err;
+		}
+		hdlcd->irq = i;
+	}
+
+	if (!request_mem_region(mem->start, resource_size(mem),	dev_name(&pdev->dev))) {
+		err = -ENXIO;
+		goto request_err;
+	}
+
+	if (!hdlcd->fb.fix.smem_start) {
+		dev_err(&pdev->dev, "platform did not allocate frame buffer memory\n");
+		err = -ENOMEM;
+		goto memalloc_err;
+	}
+
+	/* if we use CMA we might have already setup the screen_base */
+	if (!hdlcd->fb.screen_base) {
+		hdlcd->fb.screen_base = ioremap_wc(hdlcd->fb.fix.smem_start,
+						framebuffer_size);
+		if (!hdlcd->fb.screen_base) {
+			dev_err(&pdev->dev, "unable to ioremap framebuffer\n");
+			err = -ENOMEM;
+			goto probe_err;
+		}
+
+		/* Clear the framebuffer */
+		memset(hdlcd->fb.screen_base, 0, framebuffer_size);
+	}
+
+	hdlcd->fb.screen_size = framebuffer_size;
+	hdlcd->fb.fix.smem_len = framebuffer_size;
+	hdlcd->fb.fix.mmio_start = mem->start;
+	hdlcd->fb.fix.mmio_len = resource_size(mem);
+
+	hdlcd->dev = &pdev->dev;
+
+	dev_dbg(&pdev->dev, "HDLCD: framebuffer virt base %p, phys base 0x%lX\n",
+		hdlcd->fb.screen_base, (unsigned long)hdlcd->fb.fix.smem_start);
+
+	err = hdlcd_setup(hdlcd);
+
+	if (err)
+		goto probe_err;
+
+	platform_set_drvdata(pdev, hdlcd);
+	return 0;
+
+probe_err:
+#ifdef CONFIG_CMA
+	dma_free_writecombine(&pdev->dev, framebuffer_size,
+			hdlcd->fb.screen_base, hdlcd->fb.fix.smem_start);
+#else
+	iounmap(hdlcd->fb.screen_base);
+	memblock_free(hdlcd->fb.fix.smem_start, hdlcd->fb.fix.smem_start);
+#endif
+
+memalloc_err:
+	release_mem_region(mem->start, resource_size(mem));
+
+request_err:
+	free_irq(hdlcd->irq, hdlcd);
+
+resource_err:
+	kfree(hdlcd);
+
+	return err;
+}
+
+static int hdlcd_remove(struct platform_device *pdev)
+{
+	struct hdlcd_device *hdlcd = platform_get_drvdata(pdev);
+
+	clk_disable(hdlcd->clk);
+	clk_unprepare(hdlcd->clk);
+	clk_put(hdlcd->clk);
+
+	/* unmap memory */
+#ifdef CONFIG_CMA
+	dma_free_writecombine(&pdev->dev, framebuffer_size,
+			hdlcd->fb.screen_base, hdlcd->fb.fix.smem_start);
+#else
+	iounmap(hdlcd->fb.screen_base);
+	memblock_free(hdlcd->fb.fix.smem_start, hdlcd->fb.fix.smem_start);
+#endif
+	iounmap(hdlcd->base);
+
+	/* deallocate memory */
+	fb_dealloc_cmap(&hdlcd->fb.cmap);
+	kfree(hdlcd->fb.pseudo_palette);
+	release_mem_region(hdlcd->fb.fix.mmio_start, hdlcd->fb.fix.mmio_len);
+
+	free_irq(hdlcd->irq, NULL);
+	kfree(hdlcd);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int hdlcd_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	/* not implemented yet */
+	return 0;
+}
+
+static int hdlcd_resume(struct platform_device *pdev)
+{
+	/* not implemented yet */
+	return 0;
+}
+#else
+#define hdlcd_suspend	NULL
+#define hdlcd_resume	NULL
+#endif
+
+static struct platform_driver hdlcd_driver = {
+	.probe		= hdlcd_probe,
+	.remove		= __devexit_p(hdlcd_remove),
+	.suspend	= hdlcd_suspend,
+	.resume		= hdlcd_resume,
+	.driver	= {
+		.name		= "hdlcd",
+		.owner		= THIS_MODULE,
+		.of_match_table	= hdlcd_of_matches,
+	},
+};
+
+static int __init hdlcd_init(void)
+{
+	int err = platform_driver_register(&hdlcd_driver);
+
+#ifdef HDLCD_COUNT_BUFFERUNDERRUNS
+	if (!err)
+		hdlcd_underrun_init();
+#endif
+
+	return err;
+}
+
+void __exit hdlcd_exit(void)
+{
+#ifdef HDLCD_COUNT_BUFFERUNDERRUNS
+	hdlcd_underrun_close();
+#endif
+	platform_driver_unregister(&hdlcd_driver);
+}
+
+module_init(hdlcd_init);
+module_exit(hdlcd_exit);
+
+MODULE_AUTHOR("Liviu Dudau");
+MODULE_DESCRIPTION("ARM HDLCD core driver");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/arm-hdlcd.h b/include/linux/arm-hdlcd.h
new file mode 100644
index 0000000..8ab3d7f
--- /dev/null
+++ b/include/linux/arm-hdlcd.h
@@ -0,0 +1,128 @@ 
+/*
+ * include/linux/arm-hdlcd.h
+ *
+ * Copyright (C) 2012 ARM Limited
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file COPYING in the main directory of this archive
+ * for more details.
+ *
+ *  ARM HDLCD Controller register definition
+ */
+
+#include <linux/fb.h>
+#include <linux/completion.h>
+
+/* register offsets */
+#define HDLCD_REG_VERSION		0x0000	/* ro */
+#define HDLCD_REG_INT_RAWSTAT		0x0010	/* rw */
+#define HDLCD_REG_INT_CLEAR		0x0014	/* wo */
+#define HDLCD_REG_INT_MASK		0x0018	/* rw */
+#define HDLCD_REG_INT_STATUS		0x001c	/* ro */
+#define HDLCD_REG_USER_OUT		0x0020	/* rw */
+#define HDLCD_REG_FB_BASE		0x0100	/* rw */
+#define HDLCD_REG_FB_LINE_LENGTH	0x0104	/* rw */
+#define HDLCD_REG_FB_LINE_COUNT		0x0108	/* rw */
+#define HDLCD_REG_FB_LINE_PITCH		0x010c	/* rw */
+#define HDLCD_REG_BUS_OPTIONS		0x0110	/* rw */
+#define HDLCD_REG_V_SYNC		0x0200	/* rw */
+#define HDLCD_REG_V_BACK_PORCH		0x0204	/* rw */
+#define HDLCD_REG_V_DATA		0x0208	/* rw */
+#define HDLCD_REG_V_FRONT_PORCH		0x020c	/* rw */
+#define HDLCD_REG_H_SYNC		0x0210	/* rw */
+#define HDLCD_REG_H_BACK_PORCH		0x0214	/* rw */
+#define HDLCD_REG_H_DATA		0x0218	/* rw */
+#define HDLCD_REG_H_FRONT_PORCH		0x021c	/* rw */
+#define HDLCD_REG_POLARITIES		0x0220	/* rw */
+#define HDLCD_REG_COMMAND		0x0230	/* rw */
+#define HDLCD_REG_PIXEL_FORMAT		0x0240	/* rw */
+#define HDLCD_REG_BLUE_SELECT		0x0244	/* rw */
+#define HDLCD_REG_GREEN_SELECT		0x0248	/* rw */
+#define HDLCD_REG_RED_SELECT		0x024c	/* rw */
+
+/* version */
+#define HDLCD_PRODUCT_ID		0x1CDC0000
+#define HDLCD_PRODUCT_MASK		0xFFFF0000
+#define HDLCD_VERSION_MAJOR_MASK	0x0000FF00
+#define HDLCD_VERSION_MINOR_MASK	0x000000FF
+
+/* interrupts */
+#define HDLCD_INTERRUPT_DMA_END		(1 << 0)
+#define HDLCD_INTERRUPT_BUS_ERROR	(1 << 1)
+#define HDLCD_INTERRUPT_VSYNC		(1 << 2)
+#define HDLCD_INTERRUPT_UNDERRUN	(1 << 3)
+
+/* polarity */
+#define HDLCD_POLARITY_VSYNC		(1 << 0)
+#define HDLCD_POLARITY_HSYNC		(1 << 1)
+#define HDLCD_POLARITY_DATAEN		(1 << 2)
+#define HDLCD_POLARITY_DATA		(1 << 3)
+#define HDLCD_POLARITY_PIXELCLK		(1 << 4)
+
+/* commands */
+#define HDLCD_COMMAND_DISABLE		(0 << 0)
+#define HDLCD_COMMAND_ENABLE		(1 << 0)
+
+/* pixel format */
+#define HDLCD_PIXEL_FMT_LITTLE_ENDIAN	(0 << 31)
+#define HDLCD_PIXEL_FMT_BIG_ENDIAN	(1 << 31)
+#define HDLCD_BYTES_PER_PIXEL_MASK	(3 << 3)
+
+/* bus options */
+#define HDLCD_BUS_BURST_MASK		0x01f
+#define HDLCD_BUS_MAX_OUTSTAND		0xf00
+#define HDLCD_BUS_BURST_NONE		(0 << 0)
+#define HDLCD_BUS_BURST_1		(1 << 0)
+#define HDLCD_BUS_BURST_2		(1 << 1)
+#define HDLCD_BUS_BURST_4		(1 << 2)
+#define HDLCD_BUS_BURST_8		(1 << 3)
+#define HDLCD_BUS_BURST_16		(1 << 4)
+
+/* Max resolution supported is 4096x4096, 8 bit per color component,
+   8 bit alpha, but we are going to choose the usual hardware default
+   (2048x2048, 32 bpp) and enable double buffering */
+#define HDLCD_MAX_XRES			2048
+#define HDLCD_MAX_YRES			2048
+#define HDLCD_MAX_FRAMEBUFFER_SIZE	(HDLCD_MAX_XRES * HDLCD_MAX_YRES << 2)
+
+#define HDLCD_MEM_BASE			(CONFIG_PAGE_OFFSET - 0x1000000)
+
+#define NR_PALETTE	256
+
+/*
+ * Developers using HDLCD may wish to enable these settings if
+ * display disruption is apparent and you suspect HDLCD
+ * access to RAM may be starved.
+ */
+
+/*
+ * Turn HDLCD default color to red instead of default black so
+ * that it's easy to see pixel clock data underruns
+ * (compared to other visual disruption)
+ */
+#undef HDLCD_SHOW_UNDERRUN
+
+/*
+ * Add a counter in the IRQ handler to count buffer underruns
+ * and /proc/hdlcd_underrun to read the counter
+ */
+#undef HDLCD_COUNT_BUFFERUNDERRUNS
+
+/*
+ * Restrict height to 1x screen size
+ */
+#undef HDLCD_NO_VIRTUAL_SCREEN
+
+#ifdef CONFIG_ANDROID
+#define HDLCD_NO_VIRTUAL_SCREEN
+#endif
+
+struct hdlcd_device {
+	struct fb_info		fb;
+	struct device		*dev;
+	struct clk		*clk;
+	void __iomem		*base;
+	int			irq;
+	struct completion	vsync_completion;
+	unsigned char		*edid;
+};