[RFC 4/4] drm: Add NVIDIA Tegra support

Thierry Reding thierry.reding at avionic-design.de
Wed Apr 11 22:10:30 EST 2012


This commit adds a very basic DRM driver for NVIDIA Tegra SoCs. It
currently has rudimentary GEM support and can run a console on the
framebuffer as well as X using the xf86-video-modesetting driver.
Only the RGB output is supported. Quite a lot of things still need
to be worked out and there is a lot of room for cleanup.

Signed-off-by: Thierry Reding <thierry.reding at avionic-design.de>
---
 .../devicetree/bindings/gpu/drm/tegra.txt          |   24 +
 arch/arm/mach-tegra/board-dt-tegra20.c             |    3 +
 arch/arm/mach-tegra/tegra2_clocks.c                |    8 +-
 drivers/gpu/drm/Kconfig                            |    2 +
 drivers/gpu/drm/Makefile                           |    1 +
 drivers/gpu/drm/tegra/Kconfig                      |   10 +
 drivers/gpu/drm/tegra/Makefile                     |    5 +
 drivers/gpu/drm/tegra/tegra_drv.c                  | 2241 ++++++++++++++++++++
 drivers/gpu/drm/tegra/tegra_drv.h                  |  184 ++
 include/drm/tegra_drm.h                            |   44 +
 10 files changed, 2518 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/gpu/drm/tegra.txt
 create mode 100644 drivers/gpu/drm/tegra/Kconfig
 create mode 100644 drivers/gpu/drm/tegra/Makefile
 create mode 100644 drivers/gpu/drm/tegra/tegra_drv.c
 create mode 100644 drivers/gpu/drm/tegra/tegra_drv.h
 create mode 100644 include/drm/tegra_drm.h

diff --git a/Documentation/devicetree/bindings/gpu/drm/tegra.txt b/Documentation/devicetree/bindings/gpu/drm/tegra.txt
new file mode 100644
index 0000000..d39fe64
--- /dev/null
+++ b/Documentation/devicetree/bindings/gpu/drm/tegra.txt
@@ -0,0 +1,24 @@
+Example:
+
+	drm at 54200000 {
+		compatible = "nvidia,tegra20-drm";
+		reg = < 0x54200000 0x00040000    /* display A */
+		        0x54240000 0x00040000    /* display B */
+		        0x58000000 0x02000000 >; /* GART aperture */
+		interrupts = < 0 73 0x04    /* display A */
+		               0 74 0x04 >; /* display B */
+
+		lvds {
+			type = "rgb";
+			size = <345 194>;
+
+			default-mode {
+				pixel-clock = <61715000>;
+				vertical-refresh = <50>;
+				resolution = <1366 768>;
+				bits-per-pixel = <16>;
+				horizontal-timings = <4 136 2 36>;
+				vertical-timings = <2 4 21 10>;
+			};
+		};
+	};
diff --git a/arch/arm/mach-tegra/board-dt-tegra20.c b/arch/arm/mach-tegra/board-dt-tegra20.c
index bffba1b..6ce6162 100644
--- a/arch/arm/mach-tegra/board-dt-tegra20.c
+++ b/arch/arm/mach-tegra/board-dt-tegra20.c
@@ -67,6 +67,7 @@ struct of_dev_auxdata tegra20_auxdata_lookup[] __initdata = {
 		       &tegra_ehci3_pdata),
 	OF_DEV_AUXDATA("nvidia,tegra20-pwm", TEGRA_PWFM_BASE, "tegra-pwm", NULL),
 	OF_DEV_AUXDATA("nvidia,tegra20-gart", TEGRA_MC_BASE, "tegra-gart", NULL),
+	OF_DEV_AUXDATA("nvidia,tegra20-drm", TEGRA_DISPLAY_BASE, "tegra-drm", NULL),
 	{}
 };
 
@@ -81,6 +82,8 @@ static __initdata struct tegra_clk_init_table tegra_dt_clk_init_table[] = {
 	{ "cdev1",      NULL,           0,              true },
 	{ "i2s1",       "pll_a_out0",   11289600,       false},
 	{ "i2s2",       "pll_a_out0",   11289600,       false},
+	{ "host1x",     "pll_c",        144000000,      true },
+	{ "disp1",      "pll_p",        600000000,      true },
 	{ NULL,		NULL,		0,		0},
 };
 
diff --git a/arch/arm/mach-tegra/tegra2_clocks.c b/arch/arm/mach-tegra/tegra2_clocks.c
index f29084d..c86eae6 100644
--- a/arch/arm/mach-tegra/tegra2_clocks.c
+++ b/arch/arm/mach-tegra/tegra2_clocks.c
@@ -2219,8 +2219,8 @@ static struct clk tegra_list_clks[] = {
 	PERIPH_CLK("tvo",	"tvo",			NULL,	49,	0x188,	250000000, mux_pllp_plld_pllc_clkm,	MUX | DIV_U71), /* requires min voltage */
 	PERIPH_CLK("hdmi",	"hdmi",			NULL,	51,	0x18c,	600000000, mux_pllp_plld_pllc_clkm,	MUX | DIV_U71), /* requires min voltage */
 	PERIPH_CLK("tvdac",	"tvdac",		NULL,	53,	0x194,	250000000, mux_pllp_plld_pllc_clkm,	MUX | DIV_U71), /* requires min voltage */
-	PERIPH_CLK("disp1",	"tegradc.0",		NULL,	27,	0x138,	600000000, mux_pllp_plld_pllc_clkm,	MUX), /* scales with voltage and process_id */
-	PERIPH_CLK("disp2",	"tegradc.1",		NULL,	26,	0x13c,	600000000, mux_pllp_plld_pllc_clkm,	MUX), /* scales with voltage and process_id */
+	PERIPH_CLK("disp1",	"tegra-drm",		NULL,	27,	0x138,	600000000, mux_pllp_plld_pllc_clkm,	MUX), /* scales with voltage and process_id */
+	PERIPH_CLK("disp2",	"tegra-drm",		NULL,	26,	0x13c,	600000000, mux_pllp_plld_pllc_clkm,	MUX), /* scales with voltage and process_id */
 	PERIPH_CLK("usbd",	"fsl-tegra-udc",	NULL,	22,	0,	480000000, mux_clk_m,			0), /* requires min voltage */
 	PERIPH_CLK("usb2",	"tegra-ehci.1",		NULL,	58,	0,	480000000, mux_clk_m,			0), /* requires min voltage */
 	PERIPH_CLK("usb3",	"tegra-ehci.2",		NULL,	59,	0,	480000000, mux_clk_m,			0), /* requires min voltage */
@@ -2235,8 +2235,8 @@ static struct clk tegra_list_clks[] = {
 	SHARED_CLK("avp.sclk",	"tegra-avp",		"sclk",	&tegra_clk_sclk),
 	SHARED_CLK("avp.emc",	"tegra-avp",		"emc",	&tegra_clk_emc),
 	SHARED_CLK("cpu.emc",	"cpu",			"emc",	&tegra_clk_emc),
-	SHARED_CLK("disp1.emc",	"tegradc.0",		"emc",	&tegra_clk_emc),
-	SHARED_CLK("disp2.emc",	"tegradc.1",		"emc",	&tegra_clk_emc),
+	SHARED_CLK("disp1.emc",	"tegra-drm",		"emc",	&tegra_clk_emc),
+	SHARED_CLK("disp2.emc",	"tegra-drm",		"emc",	&tegra_clk_emc),
 	SHARED_CLK("hdmi.emc",	"hdmi",			"emc",	&tegra_clk_emc),
 	SHARED_CLK("host.emc",	"tegra_grhost",		"emc",	&tegra_clk_emc),
 	SHARED_CLK("usbd.emc",	"fsl-tegra-udc",	"emc",	&tegra_clk_emc),
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index e354bc0..dd543f9 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -186,3 +186,5 @@ source "drivers/gpu/drm/vmwgfx/Kconfig"
 source "drivers/gpu/drm/gma500/Kconfig"
 
 source "drivers/gpu/drm/udl/Kconfig"
+
+source "drivers/gpu/drm/tegra/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index c20da5b..d417d7e 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -42,4 +42,5 @@ obj-$(CONFIG_DRM_NOUVEAU) +=nouveau/
 obj-$(CONFIG_DRM_EXYNOS) +=exynos/
 obj-$(CONFIG_DRM_GMA500) += gma500/
 obj-$(CONFIG_DRM_UDL) += udl/
+obj-$(CONFIG_DRM_TEGRA) += tegra/
 obj-y			+= i2c/
diff --git a/drivers/gpu/drm/tegra/Kconfig b/drivers/gpu/drm/tegra/Kconfig
new file mode 100644
index 0000000..f3382c9
--- /dev/null
+++ b/drivers/gpu/drm/tegra/Kconfig
@@ -0,0 +1,10 @@
+config DRM_TEGRA
+	tristate "NVIDIA Tegra"
+	depends on DRM && ARCH_TEGRA
+	select DRM_KMS_ENCON
+	select DRM_KMS_HELPER
+	select FB_CFB_FILLRECT
+	select FB_CFB_COPYAREA
+	select FB_CFB_IMAGEBLIT
+	help
+	  Choose this option if you have an NVIDIA Tegra SoC.
diff --git a/drivers/gpu/drm/tegra/Makefile b/drivers/gpu/drm/tegra/Makefile
new file mode 100644
index 0000000..62c7e56a
--- /dev/null
+++ b/drivers/gpu/drm/tegra/Makefile
@@ -0,0 +1,5 @@
+ccflags-y := -Iinclude/drm
+
+tegra-drm-y := tegra_drv.o
+
+obj-$(CONFIG_DRM_TEGRA) += tegra-drm.o
diff --git a/drivers/gpu/drm/tegra/tegra_drv.c b/drivers/gpu/drm/tegra/tegra_drv.c
new file mode 100644
index 0000000..2c691dc
--- /dev/null
+++ b/drivers/gpu/drm/tegra/tegra_drv.c
@@ -0,0 +1,2241 @@
+/*
+ * Copyright (C) 2012 Avionic Design GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/iommu.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/clk.h>
+
+#include <linux/shmem_fs.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_fixed.h>
+#include <drm/tegra_drm.h>
+
+#include <mach/clk.h>
+#include <mach/iomap.h>
+
+#include "tegra_drv.h"
+
+#define DRIVER_NAME "tegra"
+#define DRIVER_DESC "NVIDIA Tegra graphics"
+#define DRIVER_DATE "20120330"
+#define DRIVER_MAJOR 0
+#define DRIVER_MINOR 0
+#define DRIVER_PATCHLEVEL 0
+
+static const unsigned int num_crtc = 1;
+
+struct tegra_gem_object;
+struct tegra_crtc_ops;
+
+struct tegra_crtc {
+	struct drm_crtc base;
+	int pipe;
+
+	struct clk *clk_emc;
+	struct clk *clk;
+
+	void __iomem *regs;
+	int irq;
+
+	struct drm_connector connector;
+	struct drm_encoder encoder;
+
+	struct tegra_drm_panel *panel;
+
+	const struct tegra_crtc_ops *ops;
+};
+
+static inline struct tegra_crtc *to_tegra_crtc(struct drm_crtc *crtc)
+{
+	return container_of(crtc, struct tegra_crtc, base);
+}
+
+struct tegra_crtc_ops {
+	int (*enable)(struct tegra_crtc *crtc);
+};
+
+static inline int tegra_crtc_enable(struct tegra_crtc *crtc)
+{
+	if (crtc && crtc->ops && crtc->ops->enable)
+		return crtc->ops->enable(crtc);
+
+	return crtc ? -ENOSYS : -EINVAL;
+}
+
+struct tegra_gem_object {
+	struct drm_gem_object base;
+	struct resource phys;
+	struct page **pages;
+	void *virt;
+};
+
+static inline struct tegra_gem_object *to_tegra_gem(struct drm_gem_object *obj)
+{
+	return container_of(obj, struct tegra_gem_object, base);
+}
+
+struct tegra_framebuffer {
+	struct drm_framebuffer base;
+	struct tegra_gem_object *obj;
+};
+
+static inline struct tegra_framebuffer *to_tegra_fb(struct drm_framebuffer *fb)
+{
+	return container_of(fb, struct tegra_framebuffer, base);
+}
+
+struct tegra_drm_private {
+	struct tegra_crtc crtc[2];
+	struct drm_encoder_connector *encon[2];
+	struct drm_fb_helper fb_helper;
+	struct tegra_framebuffer fb;
+	unsigned int num_crtcs;
+
+	struct iommu_domain *gart;
+	struct resource aperture;
+};
+
+static inline void tegra_crtc_writel(struct tegra_crtc *crtc, unsigned long value, unsigned long reg)
+{
+	writel(value, crtc->regs + (reg << 2));
+}
+
+static inline unsigned long tegra_crtc_readl(struct tegra_crtc *crtc, unsigned long reg)
+{
+	return readl(crtc->regs + (reg << 2));
+}
+
+static unsigned long pclk_best_div(unsigned long pclk, unsigned long rate)
+{
+	unsigned long div = DIV_ROUND_CLOSEST(rate * 2, pclk);
+	static const unsigned long max_pclk_khz = ULONG_MAX;
+
+	if (!div)
+		return 0;
+
+	while (rate * 2 / div > max_pclk_khz * 1000)
+		div++;
+
+	if (div < 2)
+		div = 2;
+
+	if (div > 257)
+		div = 257;
+
+	return div;
+}
+
+static unsigned long pclk_round_rate(struct clk *clk, unsigned long pclk, unsigned long *div)
+{
+	long rate = clk_round_rate(clk, pclk);
+
+	if (rate < 0)
+		rate = clk_get_rate(clk);
+
+	*div = pclk_best_div(pclk, rate);
+
+	return rate;
+}
+
+static int tegra_drmfb_create_handle(struct drm_framebuffer *fb, struct drm_file *filp, unsigned int *handle)
+{
+	struct tegra_framebuffer *privfb = to_tegra_fb(fb);
+	struct drm_device *drm = fb->dev;
+	int ret = 0;
+
+	dev_dbg(drm->dev, "> %s(fb=%p, filp=%p, handle=%p)\n", __func__, fb, filp, handle);
+
+	ret = drm_gem_handle_create(filp, &privfb->obj->base, handle);
+
+	dev_dbg(drm->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static void tegra_drmfb_destroy(struct drm_framebuffer *fb)
+{
+	struct tegra_framebuffer *priv = to_tegra_fb(fb);
+	struct drm_gem_object *obj = &priv->obj->base;
+	struct drm_device *drm = fb->dev;
+
+	dev_dbg(drm->dev, "> %s(fb=%p)\n", __func__, fb);
+
+	drm_framebuffer_cleanup(fb);
+	drm_gem_object_unreference_unlocked(obj);
+
+	dev_dbg(drm->dev, "< %s()\n", __func__);
+}
+
+static const struct drm_framebuffer_funcs tegra_drmfb_funcs = {
+	.create_handle = tegra_drmfb_create_handle,
+	.destroy = tegra_drmfb_destroy,
+};
+
+static int tegra_fb_init(struct drm_device *drm, struct tegra_framebuffer *fb,
+			 struct drm_mode_fb_cmd2 *mode)
+{
+	int ret = 0;
+
+	dev_dbg(drm->dev, "> %s(drm=%p, fb=%p, mode=%p)\n", __func__, drm, fb, mode);
+
+	/* TODO: add sanity checks */
+
+	ret = drm_framebuffer_init(drm, &fb->base, &tegra_drmfb_funcs);
+	if (ret < 0) {
+		DRM_ERROR("framebuffer init failed %d\n", ret);
+		return ret;
+	}
+
+	ret = drm_helper_mode_fill_fb_struct(&fb->base, mode);
+	dev_dbg(drm->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static struct drm_framebuffer *tegra_drm_fb_create(struct drm_device *drm,
+						   struct drm_file *filp,
+						   struct drm_mode_fb_cmd2 *cmd)
+{
+	struct drm_framebuffer *drmfb = NULL;
+	struct tegra_framebuffer *fb = NULL;
+	struct drm_gem_object *obj;
+	u32 bpp, depth;
+	int err;
+
+	dev_dbg(drm->dev, "> %s(drm=%p, filp=%p, cmd=%p)\n", __func__, drm,
+		filp, cmd);
+
+	drm_fb_get_bpp_depth(cmd->pixel_format, &depth, &bpp);
+
+	obj = drm_gem_object_lookup(drm, filp, cmd->handles[0]);
+	if (!obj) {
+		drmfb = ERR_PTR(-ENOENT);
+		goto out;
+	}
+
+	fb = devm_kzalloc(drm->dev, sizeof(*fb), GFP_KERNEL);
+	if (!fb) {
+		drmfb = ERR_PTR(-ENOMEM);
+		goto out;
+	}
+
+	err = tegra_fb_init(drm, fb, cmd);
+	if (err < 0) {
+		devm_kfree(drm->dev, fb);
+		drmfb = ERR_PTR(err);
+		goto out;
+	}
+
+	fb->obj = to_tegra_gem(obj);
+	drmfb = &fb->base;
+
+out:
+	dev_dbg(drm->dev, "< %s() = %p\n", __func__, drmfb);
+	return drmfb;
+}
+
+static void tegra_drm_fb_output_poll_changed(struct drm_device *drm)
+{
+	dev_dbg(drm->dev, "> %s(drm=%p)\n", __func__, drm);
+	dev_dbg(drm->dev, "< %s()\n", __func__);
+}
+
+static struct drm_mode_config_funcs tegra_drm_mode_funcs = {
+	.fb_create = tegra_drm_fb_create,
+	.output_poll_changed = tegra_drm_fb_output_poll_changed,
+};
+
+static void tegra_crtc_save(struct drm_crtc *crtc)
+{
+	dev_dbg(crtc->dev->dev, "> %s(crtc=%p)\n", __func__, crtc);
+	dev_dbg(crtc->dev->dev, "< %s()\n", __func__);
+}
+
+static void tegra_crtc_restore(struct drm_crtc *crtc)
+{
+	dev_dbg(crtc->dev->dev, "> %s(crtc=%p)\n", __func__, crtc);
+	dev_dbg(crtc->dev->dev, "< %s()\n", __func__);
+}
+
+static void tegra_crtc_gamma_set(struct drm_crtc *crtc, u16 *r, u16 *g, u16 *b,
+				 uint32_t start, uint32_t size)
+{
+	dev_dbg(crtc->dev->dev, "> %s(crtc=%p, r=%p, g=%p, b=%p, start=%u, size=%u)\n",
+		__func__, crtc, r, g, b, start, size);
+	dev_dbg(crtc->dev->dev, "< %s()\n", __func__);
+}
+
+static void tegra_crtc_destroy(struct drm_crtc *crtc)
+{
+	dev_dbg(crtc->dev->dev, "> %s(crtc=%p)\n", __func__, crtc);
+	dev_dbg(crtc->dev->dev, "< %s()\n", __func__);
+}
+
+static int tegra_crtc_page_flip(struct drm_crtc *crtc,
+				struct drm_framebuffer *fb,
+				struct drm_pending_vblank_event *event)
+{
+	int ret = 0;
+	dev_dbg(crtc->dev->dev, "> %s(crtc=%p, fb=%p, event=%p)\n", __func__, crtc, fb, event);
+	dev_dbg(crtc->dev->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static const struct drm_crtc_funcs tegra_crtc_funcs = {
+	.save = tegra_crtc_save,
+	.restore = tegra_crtc_restore,
+	.gamma_set = tegra_crtc_gamma_set,
+	.set_config = drm_crtc_helper_set_config,
+	.destroy = tegra_crtc_destroy,
+	.page_flip = tegra_crtc_page_flip,
+};
+
+static void tegra_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+	dev_dbg(crtc->dev->dev, "> %s(crtc=%p, mode=%d)\n", __func__, crtc, mode);
+	dev_dbg(crtc->dev->dev, "< %s()\n", __func__);
+}
+
+static bool tegra_crtc_mode_fixup(struct drm_crtc *crtc,
+				  struct drm_display_mode *mode,
+				  struct drm_display_mode *adjusted)
+{
+	bool ret = true;
+	dev_dbg(crtc->dev->dev, "> %s(crtc=%p, mode=%p, adjusted=%p)\n", __func__, crtc, mode, adjusted);
+	dev_dbg(crtc->dev->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+struct crtc_mode {
+	unsigned int width;
+	unsigned int height;
+	unsigned int href_to_sync;
+	unsigned int vref_to_sync;
+	unsigned int hsync_width;
+	unsigned int vsync_width;
+	unsigned int hback_porch;
+	unsigned int vback_porch;
+	unsigned int hfront_porch;
+	unsigned int vfront_porch;
+};
+
+struct crtc_win {
+	fixed20_12 x;
+	fixed20_12 y;
+	fixed20_12 w;
+	fixed20_12 h;
+	unsigned int outx;
+	unsigned int outy;
+	unsigned int outw;
+	unsigned int outh;
+	unsigned int stride;
+	unsigned int fmt;
+};
+
+static inline u32 compute_dda_inc(fixed20_12 inf, unsigned int out, bool v,
+				  unsigned int bpp)
+{
+	fixed20_12 outf = dfixed_init(out);
+	u32 dda_inc;
+	int max;
+
+	if (v)
+		max = 15;
+	else {
+		switch (bpp) {
+		case 2:
+			max = 8;
+			break;
+
+		default:
+			WARN_ON_ONCE(1);
+			/* fallthrough */
+		case 4:
+			max = 4;
+			break;
+		}
+	}
+
+	outf.full = max_t(u32, outf.full - dfixed_const(1), dfixed_const(1));
+	inf.full -= dfixed_const(1);
+
+	dda_inc = dfixed_div(inf, outf);
+	dda_inc = min_t(u32, dda_inc, dfixed_const(max));
+
+	return dda_inc;
+}
+
+static inline u32 compute_initial_dda(fixed20_12 in)
+{
+	return dfixed_frac(in);
+}
+
+static int tegra_crtc_set_timings(struct tegra_crtc *crtc, struct drm_display_mode *mode)
+{
+	unsigned int front_porch[2];
+	unsigned int back_porch[2];
+	unsigned int sync_width[2];
+	unsigned int active[2];
+	unsigned int reftos[2];
+
+	active[0] = mode->hdisplay;
+	active[1] = mode->vdisplay;
+	reftos[0] = 0;
+	reftos[1] = 0;
+	sync_width[0] = mode->hsync_end - mode->hsync_start;
+	sync_width[1] = mode->vsync_end - mode->vsync_start;
+	back_porch[0] = mode->hsync_start - mode->hdisplay;
+	back_porch[1] = mode->vsync_start - mode->vdisplay;
+	front_porch[0] = mode->htotal - mode->hsync_end;
+	front_porch[1] = mode->vtotal - mode->vsync_end;
+
+	tegra_crtc_writel(crtc, 0x0, DISP_TIMING_OPT);
+	tegra_crtc_writel(crtc, (reftos[1] << 16) | reftos[0], DISP_REF_TO_SYNC);
+	tegra_crtc_writel(crtc, (sync_width[1] << 16) | sync_width[0], DISP_SYNC_WIDTH);
+	tegra_crtc_writel(crtc, (back_porch[1] << 16) | back_porch[0], DISP_BACK_PORCH);
+	tegra_crtc_writel(crtc, (front_porch[1] << 16) | front_porch[0], DISP_FRONT_PORCH);
+
+	tegra_crtc_writel(crtc, (active[1] << 16) | active[0], DISP_ACTIVE);
+
+	return 0;
+}
+
+static int tegra_crtc_mode_set(struct drm_crtc *crtc,
+			       struct drm_display_mode *mode,
+			       struct drm_display_mode *adjusted,
+			       int x, int y, struct drm_framebuffer *old_fb)
+{
+	struct tegra_crtc *priv = container_of(crtc, struct tegra_crtc, base);
+	unsigned long pclk = mode->clock * 1000;
+	struct tegra_framebuffer *fb;
+	unsigned long update_mask;
+	unsigned long rate, div;
+	struct crtc_win win;
+	unsigned int h_dda;
+	unsigned int v_dda;
+	unsigned long val;
+	unsigned int bpp;
+	int ret = 0;
+
+	dev_dbg(crtc->dev->dev, "> %s(crtc=%p, mode=%p, adjusted=%p, x=%d, y=%d, old_fb=%p)\n",
+		__func__, crtc, mode, adjusted, x, y, old_fb);
+
+	fb = container_of(crtc->fb, struct tegra_framebuffer, base);
+	update_mask = CMD_STATE_CTRL_GENERAL_ACT_REQ;
+
+	rate = pclk_round_rate(priv->clk, pclk, &div);
+	dev_dbg(crtc->dev->dev, "  rate:%lu div:%lu\n", rate, div);
+	clk_set_rate(priv->clk, rate);
+
+	pclk = div ? (rate * 2 / div) : 0;
+	dev_dbg(crtc->dev->dev, "  pclk:%lu\n", pclk);
+
+	/* program display mode */
+	tegra_crtc_set_timings(priv, mode);
+
+	val = DISP_DATA_ENABLE_OPT_SELECT_ACTIVE |
+	      DISP_DATA_ENABLE_OPT_CONTROL_NORMAL;
+	tegra_crtc_writel(priv, val, DISP_DATA_ENABLE_OPT);
+
+	val = tegra_crtc_readl(priv, COM_PIN_OUTPUT_POLARITY(1));
+	val &= ~COM_PIN_OUTPUT_POLARITY_PIN1_LVS_OUTPUT;
+	val &= ~COM_PIN_OUTPUT_POLARITY_PIN1_LHS_OUTPUT;
+	tegra_crtc_writel(priv, val, COM_PIN_OUTPUT_POLARITY(1));
+
+	val = DISP_INTERFACE_CTRL_DATA_FORMAT_DF1P1C |
+	      DISP_INTERFACE_CTRL_ALIGN_MSB |
+	      DISP_INTERFACE_CTRL_ORDER_RED_BLUE;
+	tegra_crtc_writel(priv, val, DISP_INTERFACE_CTRL);
+
+	tegra_crtc_writel(priv, 0x00010001, DISP_SHIFT_CLK_OPT);
+
+	val = DISP_CLK_CTRL_CLK_DIV(div - 2) | DISP_CLK_CTRL_PIXEL_CLK_DIV_PCD1;
+	tegra_crtc_writel(priv, val, DISP_CLK_CTRL);
+
+	/* setup window parameters */
+	memset(&win, 0, sizeof(win));
+	win.x.full = dfixed_const(0);
+	win.y.full = dfixed_const(0);
+	win.w.full = dfixed_const(mode->hdisplay);
+	win.h.full = dfixed_const(mode->vdisplay);
+	win.outx = 0;
+	win.outy = 0;
+	win.outw = mode->hdisplay;
+	win.outh = mode->vdisplay;
+
+	switch (crtc->fb->pixel_format) {
+	case DRM_FORMAT_XRGB8888:
+		win.fmt = WIN_COLOR_DEPTH_R8G8B8A8;
+		break;
+
+	case DRM_FORMAT_RGB565:
+		win.fmt = WIN_COLOR_DEPTH_B5G6R5;
+		break;
+
+	default:
+		win.fmt = WIN_COLOR_DEPTH_R8G8B8A8;
+		WARN_ON(1);
+		break;
+	}
+
+	bpp = crtc->fb->bits_per_pixel / 8;
+	win.stride = win.outw * bpp;
+
+	/* program window registers */
+	val = tegra_crtc_readl(priv, CMD_WIN_HEADER);
+	val |= CMD_WIN_HEADER_WINDOW_A_SELECT;
+	tegra_crtc_writel(priv, val, CMD_WIN_HEADER);
+
+	tegra_crtc_writel(priv, win.fmt, WIN_COLOR_DEPTH);
+	tegra_crtc_writel(priv, 0, WIN_BYTE_SWAP);
+
+	val = WIN_POSITION_V(win.outy) | WIN_POSITION_H(win.outx);
+	tegra_crtc_writel(priv, val, WIN_POSITION);
+
+	val = WIN_SIZE_V(win.outh) | WIN_SIZE_H(win.outw);
+	tegra_crtc_writel(priv, val, WIN_SIZE);
+
+	val = WIN_PRESCALED_SIZE_V(dfixed_trunc(win.h)) |
+	      WIN_PRESCALED_SIZE_H(dfixed_trunc(win.w) * bpp);
+	tegra_crtc_writel(priv, val, WIN_PRESCALED_SIZE);
+
+	h_dda = compute_dda_inc(win.w, win.outw, false, bpp);
+	v_dda = compute_dda_inc(win.h, win.outh, true, bpp);
+
+	val = WIN_DDA_INC_V(v_dda) | WIN_DDA_INC_H(h_dda);
+	tegra_crtc_writel(priv, val, WIN_DDA_INC);
+
+	h_dda = compute_initial_dda(win.x);
+	v_dda = compute_initial_dda(win.y);
+
+	tegra_crtc_writel(priv, h_dda, WIN_H_INITIAL_DDA);
+	tegra_crtc_writel(priv, v_dda, WIN_V_INITIAL_DDA);
+
+	tegra_crtc_writel(priv, 0, WIN_UV_BUF_STRIDE);
+	tegra_crtc_writel(priv, 0, WIN_BUF_STRIDE);
+
+	dev_dbg(crtc->dev->dev, "%s(): displaying GEM %p @%x\n", __func__,
+		fb->obj, fb->obj->phys.start);
+
+	tegra_crtc_writel(priv, fb->obj->phys.start, WINBUF_START_ADDR);
+	tegra_crtc_writel(priv, win.stride, WIN_LINE_STRIDE);
+	tegra_crtc_writel(priv, dfixed_trunc(win.x) * bpp, WINBUF_ADDR_H_OFFSET);
+	tegra_crtc_writel(priv, dfixed_trunc(win.y), WINBUF_ADDR_V_OFFSET);
+
+	val = WIN_OPT_ENABLE;
+
+	if (bpp < 24)
+		val |= WIN_OPT_COLOR_EXPAND;
+
+	tegra_crtc_writel(priv, val, WIN_OPT);
+
+	tegra_crtc_writel(priv, 0xff00, WIN_BLEND_NOKEY);
+	tegra_crtc_writel(priv, 0xff00, WIN_BLEND_1WIN);
+
+	update_mask |= CMD_STATE_CTRL_WIN_A_ACT_REQ;
+
+	tegra_crtc_writel(priv, update_mask << 8, CMD_STATE_CTRL);
+
+	val = tegra_crtc_readl(priv, CMD_INT_ENABLE);
+	val |= INT_FRAME_END;
+	tegra_crtc_writel(priv, val, CMD_INT_ENABLE);
+
+	val = tegra_crtc_readl(priv, CMD_INT_MASK);
+	val |= INT_FRAME_END;
+	tegra_crtc_writel(priv, val, CMD_INT_MASK);
+
+	tegra_crtc_writel(priv, update_mask, CMD_STATE_CTRL);
+
+	dev_dbg(crtc->dev->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+#if 0
+static int tegra_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+				    struct drm_framebuffer *fb)
+{
+	struct tegra_framebuffer *privfb = to_tegra_fb(fb);
+	struct tegra_crtc *priv = to_tegra_crtc(crtc);
+	int ret = 0;
+
+	dev_dbg(crtc->dev->dev, "> %s(crtc=%p, x=%d, y=%d, fb=%p)\n", __func__, crtc, x, y,
+		fb);
+
+	tegra_crtc_writel(priv, privfb->phys, WINBUF_START_ADDR);
+
+	dev_dbg(crtc->dev->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+#else
+#define tegra_crtc_mode_set_base NULL
+#endif
+
+static void tegra_crtc_prepare(struct drm_crtc *crtc)
+{
+	dev_dbg(crtc->dev->dev, "> %s(crtc=%p)\n", __func__, crtc);
+	dev_dbg(crtc->dev->dev, "< %s()\n", __func__);
+}
+
+static void tegra_crtc_commit(struct drm_crtc *crtc)
+{
+	dev_dbg(crtc->dev->dev, "> %s(crtc=%p)\n", __func__, crtc);
+	dev_dbg(crtc->dev->dev, "< %s()\n", __func__);
+}
+
+static void tegra_crtc_load_lut(struct drm_crtc *crtc)
+{
+	dev_dbg(crtc->dev->dev, "> %s(crtc=%p)\n", __func__, crtc);
+	dev_dbg(crtc->dev->dev, "< %s()\n", __func__);
+}
+
+static void tegra_crtc_disable(struct drm_crtc *crtc)
+{
+	dev_dbg(crtc->dev->dev, "> %s(crtc=%p)\n", __func__, crtc);
+	dev_dbg(crtc->dev->dev, "< %s()\n", __func__);
+}
+
+static const struct drm_crtc_helper_funcs tegra_crtc_helper_funcs = {
+	.dpms = tegra_crtc_dpms,
+	.mode_fixup = tegra_crtc_mode_fixup,
+	.mode_set = tegra_crtc_mode_set,
+	.mode_set_base = tegra_crtc_mode_set_base,
+	.prepare = tegra_crtc_prepare,
+	.commit = tegra_crtc_commit,
+	.load_lut = tegra_crtc_load_lut,
+	.disable = tegra_crtc_disable,
+};
+
+#define PIN_REG_COUNT 4
+#define PIN_OUTPUT_SEL_COUNT 7
+
+static const u32 rgb_enable_tab[PIN_REG_COUNT] = {
+	0x00000000,
+	0x00000000,
+	0x00000000,
+	0x00000000,
+};
+
+static const u32 rgb_polarity_tab[PIN_REG_COUNT] = {
+	0x00000000,
+	0x01000000,
+	0x00000000,
+	0x00000000,
+};
+
+static const u32 rgb_data_tab[PIN_REG_COUNT] = {
+	0x00000000,
+	0x00000000,
+	0x00000000,
+	0x00000000,
+};
+
+static const u32 rgb_sel_tab[PIN_OUTPUT_SEL_COUNT] = {
+	0x00000000,
+	0x00000000,
+	0x00000000,
+	0x00000000,
+	0x00210222,
+	0x00002200,
+	0x00020000,
+};
+
+static int tegra_connector_get_modes(struct drm_connector *connector)
+{
+	struct tegra_crtc *crtc = container_of(connector, struct tegra_crtc, connector);
+	struct tegra_drm_panel *panel = crtc->panel;
+	unsigned int i;
+	int ret = 0;
+
+	dev_dbg(&connector->kdev, "> %s(connector=%p)\n", __func__, connector);
+
+	if (!panel) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	dev_dbg(&connector->kdev, "  panel: %d\n", panel->type);
+	dev_dbg(&connector->kdev, "    size: %ux%u\n", panel->width,
+		panel->height);
+	dev_dbg(&connector->kdev, "    modes: %u\n", panel->num_modes);
+
+	for (i = 0; i < panel->num_modes; i++) {
+		struct tegra_drm_mode *mode = &panel->modes[i];
+		struct drm_display_mode *display_mode;
+
+		display_mode = drm_mode_create(connector->dev);
+		if (!display_mode) {
+			ret = -ENOMEM;
+			goto out;
+		}
+
+		display_mode->width_mm = panel->width;
+		display_mode->height_mm = panel->height;
+
+		display_mode->clock = mode->pixel_clock / 1000;
+		display_mode->vrefresh = mode->vrefresh;
+		display_mode->hdisplay = mode->width;
+		display_mode->vdisplay = mode->height;
+
+		display_mode->hsync_start = mode->width + mode->hback_porch;
+		display_mode->hsync_end = display_mode->hsync_start + mode->hsync_width;
+		display_mode->htotal = display_mode->hsync_end + mode->hfront_porch;
+
+		display_mode->vsync_start = mode->height + mode->vback_porch;
+		display_mode->vsync_end = display_mode->vsync_start + mode->vsync_width;
+		display_mode->vtotal = display_mode->vsync_end + mode->vfront_porch;
+
+		drm_mode_set_name(display_mode);
+		drm_mode_probed_add(connector, display_mode);
+	}
+
+	ret = panel->num_modes;
+
+out:
+	dev_dbg(&connector->kdev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static int tegra_connector_mode_valid(struct drm_connector *connector,
+				      struct drm_display_mode *mode)
+{
+	int ret = MODE_OK;
+	dev_dbg(&connector->kdev, "> %s(connector=%p, mode=%p)\n", __func__, connector, mode);
+	dev_dbg(&connector->kdev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static struct drm_encoder *tegra_connector_best_encoder(struct drm_connector *connector)
+{
+	struct tegra_crtc *crtc = container_of(connector, struct tegra_crtc, connector);
+	struct drm_encoder *ret = &crtc->encoder;
+	dev_dbg(&connector->kdev, "> %s(connector=%p)\n", __func__, connector);
+	dev_dbg(&connector->kdev, "< %s() = %p\n", __func__, ret);
+	return ret;
+}
+
+static const struct drm_connector_helper_funcs connector_helper_funcs = {
+	.get_modes = tegra_connector_get_modes,
+	.mode_valid = tegra_connector_mode_valid,
+	.best_encoder = tegra_connector_best_encoder,
+};
+
+static void tegra_connector_dpms(struct drm_connector *connector, int mode)
+{
+	dev_dbg(&connector->kdev, "> %s(connector=%p, mode=%d)\n", __func__, connector, mode);
+	dev_dbg(&connector->kdev, "< %s()\n", __func__);
+}
+
+static void tegra_connector_save(struct drm_connector *connector)
+{
+	dev_dbg(&connector->kdev, "> %s(connector=%p)\n", __func__, connector);
+	dev_dbg(&connector->kdev, "< %s()\n", __func__);
+}
+
+static void tegra_connector_restore(struct drm_connector *connector)
+{
+	dev_dbg(&connector->kdev, "> %s(connector=%p)\n", __func__, connector);
+	dev_dbg(&connector->kdev, "< %s()\n", __func__);
+}
+
+static void tegra_connector_reset(struct drm_connector *connector)
+{
+	dev_dbg(&connector->kdev, "> %s(connector=%p)\n", __func__, connector);
+	dev_dbg(&connector->kdev, "< %s()\n", __func__);
+}
+
+static enum drm_connector_status tegra_connector_detect(struct drm_connector *connector, bool force)
+{
+	enum drm_connector_status status = connector_status_unknown;
+
+	dev_dbg(&connector->kdev, "> %s(connector=%p, force=%d)\n", __func__, connector, force);
+
+	if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS)
+		status = connector_status_connected;
+
+	dev_dbg(&connector->kdev, "< %s() = %d\n", __func__, status);
+	return status;
+}
+
+static int tegra_connector_fill_modes(struct drm_connector *connector,
+				      uint32_t max_width, uint32_t max_height)
+{
+	int ret = 0;
+
+	dev_dbg(&connector->kdev, "> %s(connector=%p, max_width=%u, max_height=%u)\n", __func__,
+		connector, max_width, max_height);
+
+	ret = drm_helper_probe_single_connector_modes(connector, max_width, max_height);
+
+	dev_dbg(&connector->kdev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static int tegra_connector_set_property(struct drm_connector *connector,
+					struct drm_property *property,
+					uint64_t value)
+{
+	int ret = 0;
+	dev_dbg(&connector->kdev, "> %s(connector=%p, property=%p, value=%llx)\n", __func__,
+		connector, property, value);
+	dev_dbg(&connector->kdev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static void tegra_connector_destroy(struct drm_connector *connector)
+{
+	pr_debug("> %s(connector=%p)\n", __func__, connector);
+	drm_sysfs_connector_remove(connector);
+	drm_connector_cleanup(connector);
+	pr_debug("< %s()\n", __func__);
+}
+
+static void tegra_connector_force(struct drm_connector *connector)
+{
+	dev_dbg(&connector->kdev, "> %s(connector=%p)\n", __func__, connector);
+	dev_dbg(&connector->kdev, "< %s()\n", __func__);
+}
+
+static const struct drm_connector_funcs connector_funcs = {
+	.dpms = tegra_connector_dpms,
+	.save = tegra_connector_save,
+	.restore = tegra_connector_restore,
+	.reset = tegra_connector_reset,
+
+	.detect = tegra_connector_detect,
+	.fill_modes = tegra_connector_fill_modes,
+	.set_property = tegra_connector_set_property,
+	.destroy = tegra_connector_destroy,
+	.force = tegra_connector_force,
+};
+
+static void tegra_encoder_reset(struct drm_encoder *encoder)
+{
+	dev_dbg(encoder->dev->dev, "> %s(encoder=%p)\n", __func__, encoder);
+	dev_dbg(encoder->dev->dev, "< %s()\n", __func__);
+}
+
+static void tegra_encoder_destroy(struct drm_encoder *encoder)
+{
+	dev_dbg(encoder->dev->dev, "> %s(encoder=%p)\n", __func__, encoder);
+	dev_dbg(encoder->dev->dev, "< %s()\n", __func__);
+}
+
+static const struct drm_encoder_funcs encoder_funcs = {
+	.reset = tegra_encoder_reset,
+	.destroy = tegra_encoder_destroy,
+};
+
+static void tegra_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+	dev_dbg(encoder->dev->dev, "> %s(encoder=%p, mode=%d)\n", __func__, encoder, mode);
+	dev_dbg(encoder->dev->dev, "< %s()\n", __func__);
+}
+
+static void tegra_encoder_save(struct drm_encoder *encoder)
+{
+	dev_dbg(encoder->dev->dev, "> %s(encoder=%p)\n", __func__, encoder);
+	dev_dbg(encoder->dev->dev, "< %s()\n", __func__);
+}
+
+static void tegra_encoder_restore(struct drm_encoder *encoder)
+{
+	dev_dbg(encoder->dev->dev, "> %s(encoder=%p)\n", __func__, encoder);
+	dev_dbg(encoder->dev->dev, "< %s()\n", __func__);
+}
+
+static bool tegra_encoder_mode_fixup(struct drm_encoder *encoder,
+				     struct drm_display_mode *mode,
+				     struct drm_display_mode *adjusted)
+{
+	bool ret = true;
+	dev_dbg(encoder->dev->dev, "> %s(encoder=%p, mode=%p, adjusted=%p)\n", __func__, encoder, mode, adjusted);
+	dev_dbg(encoder->dev->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static void tegra_encoder_prepare(struct drm_encoder *encoder)
+{
+	dev_dbg(encoder->dev->dev, "> %s(encoder=%p)\n", __func__, encoder);
+	dev_dbg(encoder->dev->dev, "< %s()\n", __func__);
+}
+
+static void tegra_encoder_commit(struct drm_encoder *encoder)
+{
+	dev_dbg(encoder->dev->dev, "> %s(encoder=%p)\n", __func__, encoder);
+	dev_dbg(encoder->dev->dev, "< %s()\n", __func__);
+}
+
+static void tegra_encoder_mode_set(struct drm_encoder *encoder,
+				   struct drm_display_mode *mode,
+				   struct drm_display_mode *adjusted)
+{
+	dev_dbg(encoder->dev->dev, "> %s(encoder=%p, mode=%p, adjusted=%p)\n", __func__, encoder,
+		mode, adjusted);
+	dev_dbg(encoder->dev->dev, "< %s()\n", __func__);
+}
+
+static struct drm_crtc *tegra_encoder_get_crtc(struct drm_encoder *encoder)
+{
+	struct tegra_crtc *crtc = container_of(encoder, struct tegra_crtc, encoder);
+	struct drm_crtc *ret = &crtc->base;
+	dev_dbg(encoder->dev->dev, "> %s(encoder=%p)\n", __func__, encoder);
+	dev_dbg(encoder->dev->dev, "< %s() = %p\n", __func__, ret);
+	return ret;
+}
+
+static enum drm_connector_status tegra_encoder_detect(struct drm_encoder *encoder,
+						      struct drm_connector *connector)
+{
+	enum drm_connector_status status = connector_status_unknown;
+	dev_dbg(encoder->dev->dev, "> %s(encoder=%p, connector=%p)\n", __func__, encoder, connector);
+	dev_dbg(encoder->dev->dev, "< %s() = %d\n", __func__, status);
+	return status;
+}
+
+static void tegra_encoder_disable(struct drm_encoder *encoder)
+{
+	dev_dbg(encoder->dev->dev, "> %s(encoder=%p)\n", __func__, encoder);
+	dev_dbg(encoder->dev->dev, "< %s()\n", __func__);
+}
+
+static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
+	.dpms = tegra_encoder_dpms,
+	.save = tegra_encoder_save,
+	.restore = tegra_encoder_restore,
+	.mode_fixup = tegra_encoder_mode_fixup,
+	.prepare = tegra_encoder_prepare,
+	.commit = tegra_encoder_commit,
+	.mode_set = tegra_encoder_mode_set,
+	.get_crtc = tegra_encoder_get_crtc,
+	.detect = tegra_encoder_detect,
+	.disable = tegra_encoder_disable,
+};
+
+static int tegra_crtc_rgb_enable(struct tegra_crtc *crtc)
+{
+	unsigned int i;
+
+	/* enable RGB output */
+	for (i = 0; i < PIN_REG_COUNT; i++) {
+		tegra_crtc_writel(crtc, rgb_enable_tab[i], COM_PIN_OUTPUT_ENABLE(i));
+		tegra_crtc_writel(crtc, rgb_polarity_tab[i], COM_PIN_OUTPUT_POLARITY(i));
+		tegra_crtc_writel(crtc, rgb_data_tab[i], COM_PIN_OUTPUT_DATA(i));
+	}
+
+	for (i = 0; i < PIN_OUTPUT_SEL_COUNT; i++)
+		tegra_crtc_writel(crtc, rgb_sel_tab[i], COM_PIN_OUTPUT_SEL(i));
+
+	return 0;
+}
+
+static const struct tegra_crtc_ops tegra_crtc_rgb_ops = {
+	.enable = tegra_crtc_rgb_enable,
+};
+
+static int tegra_crtc_init(struct drm_device *drm, unsigned int pipe)
+{
+	struct tegra_drm_platform_data *pdata = drm->dev->platform_data;
+	unsigned int syncpt = pipe ? SYNCPT_VBLANK1 : SYNCPT_VBLANK0;
+	struct tegra_drm_private *priv = drm->dev_private;
+	struct tegra_crtc *crtc = &priv->crtc[pipe];
+	struct tegra_drm_panel *panel;
+	unsigned long val;
+	int connector;
+	int encoder;
+	int ret = 0;
+
+	dev_dbg(drm->dev, "> %s(drm=%p, pipe=%u)\n", __func__, drm, pipe);
+
+	if (pipe >= pdata->num_panels) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	panel = crtc->panel = &pdata->panels[pipe];
+
+	switch (panel->type) {
+	case TEGRA_DRM_PANEL_RGB:
+		connector = DRM_MODE_CONNECTOR_LVDS;
+		encoder = DRM_MODE_ENCODER_LVDS;
+		crtc->ops = &tegra_crtc_rgb_ops;
+		break;
+
+	default:
+		connector = DRM_MODE_CONNECTOR_Unknown;
+		encoder = DRM_MODE_ENCODER_NONE;
+		break;
+	}
+
+	drm_connector_helper_add(&crtc->connector, &connector_helper_funcs);
+	drm_connector_init(drm, &crtc->connector, &connector_funcs, connector);
+
+	drm_encoder_init(drm, &crtc->encoder, &encoder_funcs, encoder);
+	drm_encoder_helper_add(&crtc->encoder, &encoder_helper_funcs);
+
+	drm_mode_connector_attach_encoder(&crtc->connector,
+					  &crtc->encoder);
+	drm_sysfs_connector_add(&crtc->connector);
+
+	crtc->encoder.possible_crtcs = 1 << pipe;
+
+	/* hardware initialization */
+
+	clk_enable(crtc->clk);
+	clk_enable(crtc->clk_emc);
+	tegra_periph_reset_deassert(crtc->clk);
+	msleep(10);
+
+	/* initialize display controller */
+	tegra_crtc_writel(crtc, 0x00000100, CMD_GENERAL_INCR_SYNCPT_CTRL);
+	tegra_crtc_writel(crtc, 0x100 | syncpt, CMD_CONT_SYNCPT_VSYNC);
+
+	val = INT_WIN_A_UF | INT_WIN_B_UF | INT_WIN_C_UF | INT_WIN_A_OF;
+	tegra_crtc_writel(crtc, val, CMD_INT_TYPE);
+
+	val = INT_WIN_A_UF | INT_WIN_B_UF | INT_WIN_C_UF |
+	      INT_WIN_A_OF | INT_WIN_B_OF | INT_WIN_C_OF;
+	tegra_crtc_writel(crtc, val, CMD_INT_POLARITY);
+
+	val = CMD_DISP_POWER_CTRL_PW0_ENABLE | CMD_DISP_POWER_CTRL_PW1_ENABLE |
+	      CMD_DISP_POWER_CTRL_PW2_ENABLE | CMD_DISP_POWER_CTRL_PW3_ENABLE |
+	      CMD_DISP_POWER_CTRL_PW4_ENABLE | CMD_DISP_POWER_CTRL_PM0_ENABLE |
+	      CMD_DISP_POWER_CTRL_PM1_ENABLE;
+	tegra_crtc_writel(crtc, val, CMD_DISP_POWER_CTRL);
+
+	val = tegra_crtc_readl(crtc, CMD_DISP_CMD);
+	val |= CMD_DISP_CMD_CTRL_MODE_C_DISPLAY;
+	tegra_crtc_writel(crtc, val, CMD_DISP_CMD);
+
+	/* initialize timer */
+	tegra_crtc_writel(crtc, 0x00202020, DISP_MEM_HIGH_PRI);
+	tegra_crtc_writel(crtc, 0x00010101, DISP_MEM_HIGH_PRI_TIMER);
+
+	val = INT_VBLANK | INT_WIN_A_UF | INT_WIN_B_UF | INT_WIN_C_UF;
+	tegra_crtc_writel(crtc, val, CMD_INT_MASK);
+
+	val = INT_VBLANK | INT_WIN_A_UF | INT_WIN_B_UF | INT_WIN_C_UF;
+	tegra_crtc_writel(crtc, val, CMD_INT_ENABLE);
+
+	ret = tegra_crtc_enable(crtc);
+	if (ret < 0)
+		goto out;
+
+	drm_crtc_init(drm, &crtc->base, &tegra_crtc_funcs);
+	drm_mode_crtc_set_gamma_size(&crtc->base, 256);
+	drm_crtc_helper_add(&crtc->base, &tegra_crtc_helper_funcs);
+
+out:
+	dev_dbg(drm->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static void tegra_fb_gamma_set(struct drm_crtc *crtc, u16 red, u16 green,
+			       u16 blue, int regno)
+{
+	dev_dbg(crtc->dev->dev, "> %s(crtc=%p, red=%u, green=%u, blue=%u, regno=%d)\n",
+		__func__, crtc, red, green, blue, regno);
+	dev_dbg(crtc->dev->dev, "< %s()\n", __func__);
+}
+
+static void tegra_fb_gamma_get(struct drm_crtc *crtc, u16 *red, u16 *green,
+			       u16 *blue, int regno)
+{
+	dev_dbg(crtc->dev->dev, "> %s(crtc=%p, red=%p, green=%p, blue=%p, regno=%d)\n",
+		__func__, crtc, red, green, blue, regno);
+	dev_dbg(crtc->dev->dev, "< %s()\n", __func__);
+}
+
+static struct fb_ops tegra_fb_ops = {
+	.owner = THIS_MODULE,
+	.fb_check_var = drm_fb_helper_check_var,
+	.fb_set_par = drm_fb_helper_set_par,
+	.fb_fillrect = cfb_fillrect,
+	.fb_copyarea = cfb_copyarea,
+	.fb_imageblit = cfb_imageblit,
+	.fb_pan_display = drm_fb_helper_pan_display,
+	.fb_blank = drm_fb_helper_blank,
+	.fb_setcmap = drm_fb_helper_setcmap,
+	.fb_debug_enter = drm_fb_helper_debug_enter,
+	.fb_debug_leave = drm_fb_helper_debug_leave,
+};
+
+static int tegra_gem_get_pages(struct drm_device *drm,
+			       struct tegra_gem_object *obj, gfp_t gfp)
+{
+	struct address_space *mapping;
+	unsigned int num_pages;
+	struct inode *inode;
+	struct page *page;
+	unsigned int i;
+	int ret = 0;
+
+	dev_dbg(drm->dev, "> %s(drm=%p, obj=%p, gfp=%x)\n", __func__, drm,
+		obj, gfp);
+
+	if (obj->pages)
+		goto out;
+
+	num_pages = obj->base.size / PAGE_SIZE;
+
+	obj->pages = drm_malloc_ab(num_pages, sizeof(page));
+	if (!obj->pages) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	inode = obj->base.filp->f_path.dentry->d_inode;
+	mapping = inode->i_mapping;
+	gfp |= mapping_gfp_mask(mapping);
+
+	for (i = 0; i < num_pages; i++) {
+		page = shmem_read_mapping_page_gfp(mapping, i, gfp);
+		if (IS_ERR(page))
+			goto err_pages;
+
+		obj->pages[i] = page;
+	}
+
+	ret = 0;
+	goto out;
+
+err_pages:
+	while (i--)
+		page_cache_release(obj->pages[i]);
+
+	ret = PTR_ERR(page);
+	drm_free_large(obj->pages);
+	obj->pages = NULL;
+out:
+	dev_dbg(drm->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static void tegra_gem_put_pages(struct drm_device *drm,
+				struct tegra_gem_object *obj)
+{
+	unsigned int num = obj->base.size / PAGE_SIZE;
+
+	dev_dbg(drm->dev, "> %s(drm=%p, obj=%p)\n", __func__, drm, obj);
+
+	while (num--)
+		page_cache_release(obj->pages[num]);
+
+	drm_free_large(obj->pages);
+
+	dev_dbg(drm->dev, "< %s()\n", __func__);
+}
+
+static int tegra_gem_map(struct drm_device *drm, struct tegra_gem_object *obj)
+{
+	unsigned int num_pages = obj->base.size / PAGE_SIZE;
+	int ret = -ENOSYS;
+	pgprot_t prot;
+
+	dev_dbg(drm->dev, "> %s(drm=%p, obj=%p)\n", __func__, drm, obj);
+
+	ret = tegra_gem_get_pages(drm, obj, GFP_KERNEL);
+	if (ret < 0)
+		goto out;
+
+	dev_dbg(drm->dev, "  num_pages: %u\n", num_pages);
+
+	prot = pgprot_writecombine(pgprot_kernel);
+
+	obj->virt = vmap(obj->pages, num_pages, 0, prot);
+	if (!obj->virt) {
+		tegra_gem_put_pages(drm, obj);
+		ret = -ENOMEM;
+	}
+
+	dev_dbg(drm->dev, "  virt: %p\n", obj->virt);
+
+out:
+	dev_dbg(drm->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static void tegra_gem_unmap(struct drm_device *drm,
+			    struct tegra_gem_object *obj)
+{
+	dev_dbg(drm->dev, "> %s(drm=%p, obj=%p)\n", __func__, drm, obj);
+
+	vunmap(obj->virt);
+	tegra_gem_put_pages(drm, obj);
+
+	dev_dbg(drm->dev, "< %s()\n", __func__);
+}
+
+static int tegra_gem_gart_map(struct drm_device *drm,
+			      struct tegra_gem_object *obj)
+{
+	unsigned int num_pages = obj->base.size / PAGE_SIZE;
+	struct tegra_drm_private *priv = drm->dev_private;
+	resource_size_t min = priv->aperture.start;
+	resource_size_t max = priv->aperture.end;
+	resource_size_t size = obj->base.size;
+	phys_addr_t iova;
+	unsigned int i;
+	int ret = 0;
+
+	dev_dbg(drm->dev, "> %s(drm=%p, obj=%p)\n", __func__, drm, obj);
+
+	if (!priv->gart) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	ret = allocate_resource(&priv->aperture, &obj->phys, size, min, max,
+				PAGE_SIZE, NULL, NULL);
+	if (ret < 0)
+		goto out;
+
+	dev_dbg(drm->dev, "  allocation: %#x-%#x\n", obj->phys.start,
+		obj->phys.end);
+	iova = obj->phys.start;
+
+	for (i = 0; i < num_pages; i++) {
+		struct page *page = obj->pages[i];
+		phys_addr_t phys;
+		int err;
+
+		phys = page_to_phys(page);
+
+		err = iommu_map(priv->gart, iova, phys, PAGE_SIZE, 0);
+		if (err < 0)
+			dev_err(drm->dev, "iommu_map(): %d\n", err);
+
+		iova += PAGE_SIZE;
+	}
+
+out:
+	dev_dbg(drm->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static void tegra_gem_gart_unmap(struct drm_device *drm,
+				 struct tegra_gem_object *obj)
+{
+	struct tegra_drm_private *priv = drm->dev_private;
+	unsigned int num = obj->base.size / PAGE_SIZE;
+	phys_addr_t iova = obj->phys.start;
+
+	dev_dbg(drm->dev, "> %s(drm=%p, obj=%p)\n", __func__, drm, obj);
+
+	while (num--) {
+		iommu_unmap(priv->gart, iova, PAGE_SIZE);
+		iova += PAGE_SIZE;
+	}
+
+	release_resource(&obj->phys);
+
+	dev_dbg(drm->dev, "< %s()\n", __func__);
+}
+
+static struct tegra_gem_object *tegra_gem_alloc(struct drm_device *drm,
+						size_t size)
+{
+	struct tegra_gem_object *obj;
+	int err;
+
+	dev_dbg(drm->dev, "> %s(drm=%p, size=%zu)\n", __func__, drm, size);
+
+	obj = kzalloc(sizeof(*obj), GFP_KERNEL);
+	if (!obj)
+		goto out;
+
+	err = drm_gem_object_init(drm, &obj->base, size);
+	if (err < 0) {
+		kfree(obj);
+		goto out;
+	}
+
+	err = tegra_gem_map(drm, obj);
+	if (err < 0) {
+		dev_err(drm->dev, "tegra_gem_vmap(): %d\n", err);
+		kfree(obj);
+		obj = NULL;
+		goto out;
+	}
+
+	err = tegra_gem_gart_map(drm, obj);
+	if (err < 0) {
+		dev_err(drm->dev, "tegra_gem_gart_map(): %d\n", err);
+		tegra_gem_unmap(drm, obj);
+		kfree(obj);
+		obj = NULL;
+		goto out;
+	}
+
+	dev_dbg(drm->dev, "%s(): GEM allocated: %p @%#x/%p\n", __func__,
+		obj, obj->phys.start, obj->virt);
+
+out:
+	dev_dbg(drm->dev, "< %s() = %p\n", __func__, obj);
+	return obj;
+}
+
+static void tegra_gem_free(struct drm_device *drm,
+			   struct tegra_gem_object *obj)
+{
+	dev_dbg(drm->dev, "> %s(drm=%p, obj=%p)\n", __func__, drm, obj);
+
+	tegra_gem_gart_unmap(drm, obj);
+	tegra_gem_unmap(drm, obj);
+	drm_gem_object_release(&obj->base);
+	kfree(obj);
+
+	dev_dbg(drm->dev, "< %s()\n", __func__);
+}
+
+static int tegra_fb_create(struct drm_fb_helper *helper,
+			   struct drm_fb_helper_surface_size *sizes)
+{
+	struct drm_device *drm = helper->dev;
+	struct tegra_drm_private *priv = drm->dev_private;
+	struct drm_framebuffer *drmfb;
+	struct drm_mode_fb_cmd2 mode;
+	struct tegra_gem_object *obj;
+	struct fb_info *info;
+	size_t size;
+	int ret = 0;
+	u32 depth;
+	u32 bpp;
+
+	dev_dbg(drm->dev, "> %s(helper=%p, sizes=%p)\n", __func__, helper, sizes);
+	dev_dbg(drm->dev, "  sizes:\n");
+	dev_dbg(drm->dev, "    fb: %ux%u\n", sizes->fb_width, sizes->fb_height);
+	dev_dbg(drm->dev, "    surface: %ux%u (bpp:%u, depth=%u)\n",
+			sizes->surface_width, sizes->surface_height,
+			sizes->surface_bpp, sizes->surface_depth);
+
+	mode.width = sizes->surface_width;
+	mode.height = sizes->surface_height;
+
+	depth = sizes->surface_depth;
+	bpp = sizes->surface_bpp;
+
+	if (bpp == 24)
+		bpp = 32;
+
+	mode.pitches[0] = mode.width * DIV_ROUND_UP(bpp, 8);
+	size = mode.pitches[0] * mode.height;
+	size = ALIGN(size, PAGE_SIZE);
+
+	info = framebuffer_alloc(0, drm->dev);
+	if (!info) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	info->par = helper;
+
+	dev_dbg(drm->dev, "  bpp:%u depth:%u\n", bpp, depth);
+
+	mode.pixel_format = drm_mode_legacy_fb_format(bpp, depth);
+
+	ret = tegra_fb_init(drm, &priv->fb, &mode);
+	if (ret < 0)
+		goto out;
+
+	strcpy(info->fix.id, "tegra-drm-fb");
+
+	drmfb = &priv->fb.base;
+	priv->fb_helper.fb = drmfb;
+	priv->fb_helper.fbdev = info;
+
+	info->flags = FBINFO_DEFAULT | FBINFO_CAN_FORCE_OUTPUT;
+	info->fbops = &tegra_fb_ops;
+
+	obj = tegra_gem_alloc(drm, size);
+	if (!obj) {
+		dev_err(drm->dev, "tegra_gem_alloc_object() failed\n");
+		return -ENOMEM;
+	}
+
+	dev_dbg(drm->dev, "  GEM object: %p\n", obj);
+
+	info->screen_base = obj->virt;
+	priv->fb.obj = obj;
+
+	memset(info->screen_base, 0, size);
+
+	info->fix.smem_start = priv->fb.obj->phys.start;
+	info->fix.smem_len = size;
+
+	ret = fb_alloc_cmap(&info->cmap, 256, 0);
+	if (ret < 0) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	info->screen_size = size;
+
+	drm_fb_helper_fill_fix(info, drmfb->pitches[0], drmfb->depth);
+	drm_fb_helper_fill_var(info, &priv->fb_helper, sizes->fb_width,
+			       sizes->fb_height);
+
+	dev_info(drm->dev, "allocated %ux%u\n", drmfb->width, drmfb->height);
+
+out:
+	dev_dbg(drm->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static int tegra_fb_probe(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes)
+{
+	int ret = 0;
+
+	dev_dbg(helper->dev->dev, "> %s(helper=%p, sizes=%p)\n", __func__, helper, sizes);
+
+	if (!helper->fb) {
+		ret = tegra_fb_create(helper, sizes);
+		if (ret == 0)
+			ret = 1;
+	}
+
+	dev_dbg(helper->dev->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static struct drm_fb_helper_funcs tegra_fb_helper_funcs = {
+	.gamma_set = tegra_fb_gamma_set,
+	.gamma_get = tegra_fb_gamma_get,
+	.fb_probe = tegra_fb_probe,
+};
+
+static int tegra_drm_fb_init(struct drm_device *drm)
+{
+	struct tegra_drm_private *priv = drm->dev_private;
+	unsigned int i;
+	int ret = 0;
+
+	dev_dbg(drm->dev, "> %s(drm=%p)\n", __func__, drm);
+
+	drm_mode_config_init(drm);
+
+	drm->mode_config.min_width = 0;
+	drm->mode_config.min_height = 0;
+
+	drm->mode_config.max_width = 4096;
+	drm->mode_config.max_height = 4096;
+
+	drm->mode_config.funcs = &tegra_drm_mode_funcs;
+
+	drm->mode_config.fb_base = 0xdeadbeef;
+
+	for (i = 0; i < num_crtc; i++) {
+		ret = tegra_crtc_init(drm, i);
+		if (ret < 0)
+			goto out;
+
+		priv->num_crtcs++;
+	}
+
+	priv->fb_helper.funcs = &tegra_fb_helper_funcs;
+
+	ret = drm_fb_helper_init(drm, &priv->fb_helper, priv->num_crtcs,
+				 priv->num_crtcs);
+	if (ret < 0)
+		goto out;
+
+	ret = drm_fb_helper_single_add_all_connectors(&priv->fb_helper);
+	if (ret < 0)
+		goto out;
+
+	ret = drm_fb_helper_initial_config(&priv->fb_helper, 32);
+	if (ret < 0)
+		goto out;
+
+#ifndef CONFIG_FRAMEBUFFER_CONSOLE
+	ret = drm_fb_helper_restore_fbdev_mode(&priv->fb_helper);
+	if (ret < 0)
+		goto out;
+#endif
+
+	ret = 0;
+
+out:
+	dev_dbg(drm->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static irqreturn_t tegra_drm_irq(int irq, void *data)
+{
+	struct tegra_crtc *crtc = data;
+	unsigned long underflow;
+	unsigned long status;
+
+	dev_dbg(&crtc->connector.kdev, "> %s(irq=%d, data=%p)\n", __func__, irq, data);
+
+	status = tegra_crtc_readl(crtc, CMD_INT_STATUS);
+	tegra_crtc_writel(crtc, status, CMD_INT_STATUS);
+
+	dev_dbg(&crtc->connector.kdev, "  status: %#lx\n", status);
+
+	if (status & INT_FRAME_END)
+		dev_dbg(&crtc->connector.kdev, "%s(): frame end\n", __func__);
+
+	if (status & INT_VBLANK) {
+		dev_dbg(&crtc->connector.kdev, "%s(): V-blank\n", __func__);
+		drm_handle_vblank(crtc->connector.dev, crtc->pipe);
+	}
+
+	underflow = INT_WIN_A_UF | INT_WIN_B_UF | INT_WIN_C_UF;
+
+	if (status & underflow)
+		dev_dbg(&crtc->connector.kdev, "%s(): underflow\n", __func__);
+
+	dev_dbg(&crtc->connector.kdev, "< %s()\n", __func__);
+	return IRQ_HANDLED;
+}
+
+static int tegra_drm_load(struct drm_device *drm, unsigned long flags)
+{
+	struct platform_device *pdev = drm->platformdev;
+	struct tegra_drm_private *priv;
+	unsigned int i;
+	int ret = 0;
+
+	dev_dbg(drm->dev, "> %s(drm=%p, flags=%lx)\n", __func__, drm, flags);
+
+	priv = devm_kzalloc(drm->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	if (drm->dev->of_node) {
+		ret = of_address_to_resource(drm->dev->of_node, 2,
+					     &priv->aperture);
+		if (ret < 0) {
+			dev_err(drm->dev, "of_address_to_resource(): %d\n", ret);
+			return ret;
+		}
+	}
+
+	if (iommu_present(drm->dev->bus)) {
+		priv->gart = iommu_domain_alloc(drm->dev->bus);
+		if (!priv->gart) {
+			ret = -ENOMEM;
+			goto out;
+		}
+
+		ret = iommu_attach_device(priv->gart, drm->dev);
+		if (ret < 0) {
+			dev_err(drm->dev, "iommu_domain_attach_device(): %d\n", ret);
+			goto out;
+		}
+	}
+
+	drm->dev_private = priv;
+
+	for (i = 0; i < num_crtc; i++) {
+		struct tegra_crtc *crtc = &priv->crtc[i];
+		struct resource *regs;
+
+		dev_dbg(drm->dev, "  initializing CRTC %u: %p\n", i, crtc);
+
+		crtc->clk = clk_get(drm->dev, NULL);
+		if (IS_ERR_OR_NULL(crtc->clk)) {
+			DRM_ERROR("failed to get clock\n");
+			ret = -ENXIO;
+			goto out;
+		}
+
+		crtc->clk_emc = clk_get(drm->dev, "emc");
+		if (IS_ERR_OR_NULL(crtc->clk_emc)) {
+			DRM_ERROR("failed to get EMC clock\n");
+			ret = -ENXIO;
+			goto out;
+		}
+
+		regs = platform_get_resource(pdev, IORESOURCE_MEM, i);
+		if (!regs) {
+			DRM_ERROR("failed to get registers\n");
+			ret = -ENXIO;
+			goto out;
+		}
+
+		crtc->regs = devm_request_and_ioremap(drm->dev, regs);
+		if (!crtc->regs) {
+			DRM_ERROR("failed to remap registers\n");
+			ret = -ENXIO;
+			goto out;
+		}
+
+		crtc->irq = platform_get_irq(pdev, i);
+		if (crtc->irq < 0) {
+			DRM_ERROR("failed to get IRQ\n");
+			ret = -ENXIO;
+			goto out;
+		}
+
+		crtc->pipe = i;
+
+		ret = devm_request_irq(drm->dev, crtc->irq, tegra_drm_irq,
+				       0, "tegra-drm", crtc);
+		if (ret < 0) {
+			DRM_ERROR("devm_request_irq(): %d\n", ret);
+			goto out;
+		}
+	}
+
+	ret = tegra_drm_fb_init(drm);
+	if (ret < 0) {
+		dev_dbg(drm->dev, "  tegra_drm_fb_init(): %d\n", ret);
+		goto out;
+	}
+
+	drm_kms_helper_poll_init(drm);
+
+out:
+	dev_dbg(drm->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static void tegra_drm_fbdev_fini(struct drm_device *drm)
+{
+	struct tegra_drm_private *priv = drm->dev_private;
+	struct drm_fb_helper *fb_helper = &priv->fb_helper;
+
+	dev_dbg(drm->dev, "> %s(drm=%p)\n", __func__, drm);
+	dev_dbg(drm->dev, "  fbdev: %p\n", fb_helper->fb);
+
+	if (fb_helper->fbdev) {
+		struct fb_info *info = fb_helper->fbdev;
+		int err;
+
+		dev_dbg(drm->dev, "  unregistering framebuffer...\n");
+
+		err = unregister_framebuffer(info);
+		if (err < 0)
+			dev_dbg(drm->dev, "unregister_framebuffer(): %d\n", err);
+
+		dev_dbg(drm->dev, "  done\n");
+
+		if (info->cmap.len) {
+			dev_dbg(drm->dev, "  deallocating cmap...\n");
+			fb_dealloc_cmap(&info->cmap);
+			dev_dbg(drm->dev, "  done\n");
+		}
+
+		dev_dbg(drm->dev, "  releasing framebuffer...\n");
+		framebuffer_release(info);
+		dev_dbg(drm->dev, "  done\n");
+	}
+
+	dev_dbg(drm->dev, "  finalizing DRM FB helper...\n");
+	drm_fb_helper_fini(fb_helper);
+	dev_dbg(drm->dev, "  done\n");
+
+	dev_dbg(drm->dev, "< %s()\n", __func__);
+}
+
+static int tegra_drm_unload(struct drm_device *drm)
+{
+	struct tegra_drm_private *priv = drm->dev_private;
+	int ret = 0;
+
+	dev_dbg(drm->dev, "> %s(drm=%p)\n", __func__, drm);
+
+	tegra_drm_fbdev_fini(drm);
+
+	dev_dbg(drm->dev, "  calling drm_kms_helper_poll_fini()...\n");
+	drm_kms_helper_poll_fini(drm);
+	dev_dbg(drm->dev, "  done\n");
+
+	dev_dbg(drm->dev, "  calling drm_mode_config_cleanup()...\n");
+	drm_mode_config_cleanup(drm);
+	dev_dbg(drm->dev, "  done\n");
+
+	if (priv->gart) {
+		iommu_detach_device(priv->gart, drm->dev);
+		iommu_domain_free(priv->gart);
+	}
+
+	devm_kfree(drm->dev, priv);
+
+	dev_dbg(drm->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static int tegra_drm_open(struct drm_device *drm, struct drm_file *filp)
+{
+	int ret = 0;
+	dev_dbg(drm->dev, "> %s(drm=%p, filp=%p)\n", __func__, drm, filp);
+	dev_dbg(drm->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static void tegra_drm_lastclose(struct drm_device *drm)
+{
+	struct tegra_drm_private *priv = drm->dev_private;
+	int err;
+
+	dev_dbg(drm->dev, "> %s(drm=%p)\n", __func__, drm);
+
+	err = drm_fb_helper_restore_fbdev_mode(&priv->fb_helper);
+	dev_dbg(drm->dev, "  drm_fb_helper_restore_fbdev_mode(): %d\n", err);
+
+	dev_dbg(drm->dev, "< %s()\n", __func__);
+}
+
+static int tegra_drm_suspend(struct drm_device *drm, pm_message_t state)
+{
+	int ret = -ENOSYS;
+	dev_dbg(drm->dev, "> %s(drm=%p, state=[%d])\n", __func__, drm, state.event);
+	dev_dbg(drm->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static int tegra_drm_resume(struct drm_device *drm)
+{
+	int ret = -ENOSYS;
+	dev_dbg(drm->dev, "> %s(drm=%p)\n", __func__, drm);
+	dev_dbg(drm->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static int tegra_drm_enable_vblank(struct drm_device *drm, int crtc)
+{
+	int ret = -ENOSYS;
+	dev_dbg(drm->dev, "> %s(drm=%p, crtc=%d)\n", __func__, drm, crtc);
+	dev_dbg(drm->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static void tegra_drm_disable_vblank(struct drm_device *drm, int crtc)
+{
+	dev_dbg(drm->dev, "> %s(drm=%p, crtc=%d)\n", __func__, drm, crtc);
+	dev_dbg(drm->dev, "< %s()\n", __func__);
+}
+
+static int tegra_gem_handle_create(struct drm_device *drm,
+				   struct drm_file *file, size_t size,
+				   unsigned long flags, u32 *handle)
+{
+	struct tegra_gem_object *obj;
+	int err = 0;
+
+	dev_dbg(drm->dev, "> %s(drm=%p, file=%p, size=%zu, flags=%#lx, handle=%p)\n",
+		__func__, drm, file, size, flags, handle);
+
+	obj = tegra_gem_alloc(drm, size);
+	if (!obj) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	err = drm_gem_handle_create(file, &obj->base, handle);
+	if (err < 0) {
+		tegra_gem_free(drm, obj);
+		goto out;
+	}
+
+	drm_gem_object_unreference(&obj->base);
+
+out:
+	dev_dbg(drm->dev, "< %s() = %d\n", __func__, err);
+	return err;
+}
+
+static inline unsigned int align_pitch(unsigned int pitch, unsigned int width, unsigned int bpp)
+{
+	return max(pitch, width * DIV_ROUND_UP(bpp, 8));
+}
+
+static int tegra_gem_dumb_create(struct drm_file *file,
+				 struct drm_device *drm,
+				 struct drm_mode_create_dumb *args)
+{
+	int ret = -ENOSYS;
+	dev_dbg(drm->dev, "> %s(file=%p, drm=%p, args=%p)\n", __func__, file, drm, args);
+
+	args->pitch = align_pitch(args->pitch, args->width, args->bpp);
+	args->size = PAGE_ALIGN(args->pitch * args->height);
+
+	ret = tegra_gem_handle_create(drm, file, args->size, 0, &args->handle);
+
+	dev_dbg(drm->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static int tegra_gem_dumb_map_offset(struct drm_file *file,
+				     struct drm_device *drm,
+				     uint32_t handle, uint64_t *offset)
+{
+	struct tegra_gem_object *gem;
+	struct drm_gem_object *obj;
+	int ret = 0;
+
+	dev_dbg(drm->dev, "> %s(file=%p, drm=%p, handle=%x, offset=%p)\n", __func__, file, drm, handle, offset);
+
+	mutex_lock(&drm->struct_mutex);
+
+	obj = drm_gem_object_lookup(drm, file, handle);
+	if (!obj) {
+		ret = -ENOENT;
+		goto out;
+	}
+
+	gem = to_tegra_gem(obj);
+
+	ret = tegra_gem_get_pages(drm, gem, GFP_KERNEL);
+	if (ret < 0)
+		goto unref;
+
+	if (!obj->map_list.map) {
+		ret = drm_gem_create_mmap_offset(obj);
+		if (ret < 0)
+			goto unref;
+	}
+
+	*offset = (u64)obj->map_list.hash.key << PAGE_SHIFT;
+
+unref:
+	drm_gem_object_unreference(obj);
+out:
+	mutex_unlock(&drm->struct_mutex);
+	dev_dbg(drm->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static int tegra_gem_dumb_destroy(struct drm_file *file,
+				  struct drm_device *drm,
+				  uint32_t handle)
+{
+	int ret = -ENOSYS;
+	dev_dbg(drm->dev, "> %s(file=%p, drm=%p, handle=%x)\n", __func__, file, drm, handle);
+	ret = drm_gem_handle_delete(file, handle);
+	dev_dbg(drm->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static struct drm_ioctl_desc tegra_drm_ioctls[] = {
+};
+
+static int tegra_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+	int ret = 0;
+
+	pr_debug("> %s(filp=%p, vma=%p)\n", __func__, filp, vma);
+
+	ret = drm_gem_mmap(filp, vma);
+	if (ret < 0)
+		goto out;
+
+	vma->vm_flags &= ~VM_PFNMAP;
+	vma->vm_flags |= VM_MIXEDMAP;
+
+out:
+	pr_debug("< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static const struct file_operations tegra_drm_fops = {
+	.owner = THIS_MODULE,
+	.open = drm_open,
+	.release = drm_release,
+	.unlocked_ioctl = drm_ioctl,
+	.mmap = tegra_drm_gem_mmap,
+	.poll = drm_poll,
+	.fasync = drm_fasync,
+	.read = drm_read,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl = tegra_compat_ioctl,
+#endif
+	.llseek = noop_llseek,
+};
+
+static int tegra_debugfs_init(struct drm_minor *minor)
+{
+	int ret = 0;
+	dev_dbg(minor->dev->dev, "> %s(minor=%p)\n", __func__, minor);
+	dev_dbg(minor->dev->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static void tegra_debugfs_cleanup(struct drm_minor *minor)
+{
+	dev_dbg(minor->dev->dev, "> %s(minor=%p)\n", __func__, minor);
+	dev_dbg(minor->dev->dev, "< %s()\n", __func__);
+}
+
+static int tegra_gem_init_object(struct drm_gem_object *obj)
+{
+	int ret = -ENOSYS;
+	dev_dbg(obj->dev->dev, "> %s(obj=%p)\n", __func__, obj);
+	dev_dbg(obj->dev->dev, "< %s() = %d\n", __func__, ret);
+	return ret;
+}
+
+static void tegra_gem_free_object(struct drm_gem_object *obj)
+{
+	struct tegra_gem_object *gem = to_tegra_gem(obj);
+	struct drm_device *drm = obj->dev;
+
+	dev_dbg(drm->dev, "> %s(obj=%p)\n", __func__, obj);
+	dev_dbg(drm->dev, "  map_list: %p\n", obj->map_list.map);
+
+	if (obj->map_list.map)
+		drm_gem_free_mmap_offset(obj);
+
+	tegra_gem_free(obj->dev, gem);
+
+	dev_dbg(drm->dev, "< %s()\n", __func__);
+}
+
+static int tegra_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+	struct drm_gem_object *obj = vma->vm_private_data;
+	struct tegra_gem_object *gem = to_tegra_gem(obj);
+	pgoff_t page_offset;
+	struct page *page;
+	int ret;
+
+	if (!gem->pages)
+		return VM_FAULT_SIGBUS;
+
+	page_offset = ((unsigned long)vmf->virtual_address - vma->vm_start)
+		>> PAGE_SHIFT;
+	page = gem->pages[page_offset];
+
+	ret = vm_insert_page(vma, (unsigned long)vmf->virtual_address, page);
+
+	switch (ret) {
+	case -EAGAIN:
+		set_need_resched();
+		/* fallthrough */
+	case 0:
+	case -ERESTARTSYS:
+	case -EINTR:
+		return VM_FAULT_NOPAGE;
+
+	case -ENOMEM:
+		return VM_FAULT_OOM;
+	}
+
+	return VM_FAULT_SIGBUS;
+}
+
+static struct vm_operations_struct tegra_gem_vm_ops = {
+	.fault = tegra_gem_fault,
+	.open = drm_gem_vm_open,
+	.close = drm_gem_vm_close,
+};
+
+static struct drm_driver drm_driver = {
+	.driver_features = DRIVER_BUS_PLATFORM | DRIVER_MODESET | DRIVER_GEM,
+	.load = tegra_drm_load,
+	.unload = tegra_drm_unload,
+	.open = tegra_drm_open,
+	.lastclose = tegra_drm_lastclose,
+
+	.suspend = tegra_drm_suspend,
+	.resume = tegra_drm_resume,
+
+	.enable_vblank = tegra_drm_enable_vblank,
+	.disable_vblank = tegra_drm_disable_vblank,
+	.reclaim_buffers = drm_core_reclaim_buffers,
+
+#ifdef CONFIG_DEBUG_FS
+	.debugfs_init = tegra_debugfs_init,
+	.debugfs_cleanup = tegra_debugfs_cleanup,
+#endif
+
+	.gem_init_object = tegra_gem_init_object,
+	.gem_free_object = tegra_gem_free_object,
+	.gem_vm_ops = &tegra_gem_vm_ops,
+
+	.dumb_create = tegra_gem_dumb_create,
+	.dumb_map_offset = tegra_gem_dumb_map_offset,
+	.dumb_destroy = tegra_gem_dumb_destroy,
+
+	.ioctls = tegra_drm_ioctls,
+	.num_ioctls = ARRAY_SIZE(tegra_drm_ioctls),
+	.fops = &tegra_drm_fops,
+
+	.name = DRIVER_NAME,
+	.desc = DRIVER_DESC,
+	.date = DRIVER_DATE,
+	.major = DRIVER_MAJOR,
+	.minor = DRIVER_MINOR,
+	.patchlevel = DRIVER_PATCHLEVEL,
+};
+
+static const struct {
+	enum tegra_drm_panel_type type;
+	const char *name;
+} tegra_drm_panel_types[] = {
+	{ TEGRA_DRM_PANEL_RGB, "rgb" },
+};
+
+static int tegra_drm_lookup_panel_type(const char *name)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(tegra_drm_panel_types); i++) {
+		if (strcasecmp(name, tegra_drm_panel_types[i].name) == 0)
+			return tegra_drm_panel_types[i].type;
+	}
+
+	return -EINVAL;
+}
+
+static int tegra_drm_parse_dt_mode(struct device *dev,
+				   struct device_node *node,
+				   struct tegra_drm_mode *mode)
+{
+	u32 resolution[2];
+	u32 timings[4];
+	u32 value;
+	int err;
+
+	err = of_property_read_u32(node, "pixel-clock", &value);
+	if (err < 0)
+		return err;
+
+	mode->pixel_clock = value;
+
+	err = of_property_read_u32(node, "vertical-refresh", &value);
+	if (err < 0)
+		return err;
+
+	mode->vrefresh = value;
+
+	err = of_property_read_u32_array(node, "resolution", resolution,
+					 ARRAY_SIZE(resolution));
+	if (err < 0)
+		return err;
+
+	mode->width = resolution[0];
+	mode->height = resolution[1];
+
+	err = of_property_read_u32(node, "bits-per-pixel", &value);
+	if (err < 0)
+		return err;
+
+	mode->bpp = value;
+
+	err = of_property_read_u32_array(node, "horizontal-timings", timings,
+					 ARRAY_SIZE(timings));
+	if (err < 0)
+		return err;
+
+	mode->href_to_sync = timings[0];
+	mode->hsync_width = timings[1];
+	mode->hback_porch = timings[2];
+	mode->hfront_porch = timings[3];
+
+	err = of_property_read_u32_array(node, "vertical-timings", timings,
+					 ARRAY_SIZE(timings));
+	if (err < 0)
+		return err;
+
+	mode->vref_to_sync = timings[0];
+	mode->vsync_width = timings[1];
+	mode->vback_porch = timings[2];
+	mode->vfront_porch = timings[3];
+
+	return 0;
+}
+
+static int tegra_drm_parse_dt_panel(struct device *dev,
+				    struct device_node *node,
+				    struct tegra_drm_panel *panel)
+{
+	struct tegra_drm_mode *mode;
+	struct device_node *child;
+	unsigned int count = 0;
+	const char *type;
+	u32 sizes[2];
+	int err;
+
+	err = of_property_read_string(node, "type", &type);
+	if (err < 0) {
+		dev_err(dev, "failed to read \"type\" property: %d\n", err);
+		return err;
+	}
+
+	err = tegra_drm_lookup_panel_type(type);
+	if (err < 0) {
+		dev_err(dev, "failed to look up panel type: %d\n", err);
+		return err;
+	}
+
+	panel->type = err;
+
+	err = of_property_read_u32_array(node, "size", sizes,
+					 ARRAY_SIZE(sizes));
+	if (err < 0) {
+		dev_err(dev, "failed to parse \"size\" property: %d\n", err);
+		return err;
+	}
+
+	panel->width = sizes[0];
+	panel->height = sizes[1];
+
+	for_each_child_of_node(node, child)
+		count++;
+
+	if (count == 0) {
+		dev_err(dev, "no modes specified\n");
+		return -EINVAL;
+	}
+
+	panel->modes = devm_kzalloc(dev, count * sizeof(*mode), GFP_KERNEL);
+	if (!panel->modes) {
+		dev_err(dev, "failed to allocate modes\n");
+		return -ENOMEM;
+	}
+
+	for_each_child_of_node(node, child) {
+		mode = &panel->modes[panel->num_modes];
+
+		err = tegra_drm_parse_dt_mode(dev, child, mode);
+		if (err < 0) {
+			dev_err(dev, "tegra_drm_parse_dt_mode(): %d\n", err);
+			continue;
+		}
+
+		panel->num_modes++;
+	}
+
+	return 0;
+}
+
+static int tegra_drm_parse_dt(struct platform_device *pdev)
+{
+	struct tegra_drm_platform_data *pdata;
+	struct device *dev = &pdev->dev;
+	struct tegra_drm_panel *panel;
+	struct device_node *child;
+	unsigned int count = 0;
+	unsigned int i, j;
+	int err;
+
+	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+	if (!pdata)
+		return -ENOMEM;
+
+	for_each_child_of_node(dev->of_node, child)
+		count++;
+
+	if (count == 0) {
+		dev_err(dev, "no panel definitions found\n");
+		return -EINVAL;
+	}
+
+	pdata->panels = devm_kzalloc(dev, count * sizeof(*panel), GFP_KERNEL);
+	if (!pdata->panels) {
+		dev_err(dev, "failed to allocate panels\n");
+		return -ENOMEM;
+	}
+
+	for_each_child_of_node(dev->of_node, child) {
+		panel = &pdata->panels[pdata->num_panels];
+
+		err = tegra_drm_parse_dt_panel(&pdev->dev, child, panel);
+		if (err < 0) {
+			dev_err(dev, "tegra_drm_parse_dt_panel(): %d\n", err);
+			continue;
+		}
+
+		pdata->num_panels++;
+	}
+
+	for (i = 0; i < pdata->num_panels; i++) {
+		struct tegra_drm_panel *panel = &pdata->panels[i];
+
+		dev_dbg(dev, "panel %u:\n", i);
+		dev_dbg(dev, "  type: %d\n", panel->type);
+		dev_dbg(dev, "  size: %ux%u\n", panel->width, panel->height);
+
+		for (j = 0; j < panel->num_modes; j++) {
+			struct tegra_drm_mode *mode = &panel->modes[j];
+
+			dev_dbg(dev, "  mode: %u\n", j);
+			dev_dbg(dev, "    pixel-clock: %u\n", mode->pixel_clock);
+			dev_dbg(dev, "    resolution: %ux%ux%u\n",
+				 mode->width, mode->height, mode->bpp);
+
+			dev_dbg(dev, "    horizontal timings:\n");
+			dev_dbg(dev, "      ref-to-sync: %u\n", mode->href_to_sync);
+			dev_dbg(dev, "      sync-width: %u\n", mode->hsync_width);
+			dev_dbg(dev, "      back porch: %u\n", mode->hback_porch);
+			dev_dbg(dev, "      front porch: %u\n", mode->hfront_porch);
+
+			dev_dbg(dev, "    vertical timings:\n");
+			dev_dbg(dev, "      ref-to-sync: %u\n", mode->vref_to_sync);
+			dev_dbg(dev, "      sync-width: %u\n", mode->vsync_width);
+			dev_dbg(dev, "      back porch: %u\n", mode->vback_porch);
+			dev_dbg(dev, "      front porch: %u\n", mode->vfront_porch);
+		}
+	}
+
+	dev->platform_data = pdata;
+	return 0;
+}
+
+static int __devinit tegra_drm_probe(struct platform_device *pdev)
+{
+	struct tegra_drm_platform_data *pdata = pdev->dev.platform_data;
+	struct device_node *node = pdev->dev.of_node;
+	int err;
+
+	dev_dbg(&pdev->dev, "> %s(pdev=%p)\n", __func__, pdev);
+
+	if (!pdata && node) {
+		err = tegra_drm_parse_dt(pdev);
+		if (err < 0)
+			goto out;
+	}
+
+	err = drm_platform_init(&drm_driver, pdev);
+
+out:
+	dev_dbg(&pdev->dev, "< %s() = %d\n", __func__, err);
+	return err;
+}
+
+static int __devexit tegra_drm_remove(struct platform_device *pdev)
+{
+	dev_dbg(&pdev->dev, "> %s(pdev=%p)\n", __func__, pdev);
+
+	drm_platform_exit(&drm_driver, pdev);
+	pdev->dev.platform_data = NULL;
+
+	dev_dbg(&pdev->dev, "< %s()\n", __func__);
+	return 0;
+}
+
+#ifdef CONFIG_OF
+static struct of_device_id tegra_drm_of_match[] __devinitdata = {
+	{ .compatible = "nvidia,tegra20-drm", },
+	{ },
+};
+#endif
+
+static struct platform_driver tegra_drm_driver = {
+	.driver = {
+		.name = "tegra-drm",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(tegra_drm_of_match),
+	},
+	.probe = tegra_drm_probe,
+	.remove = __devexit_p(tegra_drm_remove),
+};
+
+module_platform_driver(tegra_drm_driver);
+
+MODULE_AUTHOR("Thierry Reding <thierry.reding at avionic-design.de>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/tegra/tegra_drv.h b/drivers/gpu/drm/tegra/tegra_drv.h
new file mode 100644
index 0000000..c0ab341
--- /dev/null
+++ b/drivers/gpu/drm/tegra/tegra_drv.h
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2012 Avionic Design GmbH
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef TEGRA_DRV_H
+#define TEGRA_DRV_H
+
+#define CMD_GENERAL_INCR_SYNCPT_CTRL 0x001
+#define CMD_CONT_SYNCPT_VSYNC 0x028
+#define CMD_DISP_CMD 0x032
+#define CMD_DISP_CMD_CTRL_MODE_STOP (0 << 5)
+#define CMD_DISP_CMD_CTRL_MODE_C_DISPLAY (1 << 5)
+#define CMD_DISP_CMD_CTRL_MODE_NC_DISPLAY (2 << 5)
+#define CMD_DISP_POWER_CTRL 0x036
+#define CMD_DISP_POWER_CTRL_PW0_ENABLE (1 <<  0)
+#define CMD_DISP_POWER_CTRL_PW1_ENABLE (1 <<  2)
+#define CMD_DISP_POWER_CTRL_PW2_ENABLE (1 <<  4)
+#define CMD_DISP_POWER_CTRL_PW3_ENABLE (1 <<  6)
+#define CMD_DISP_POWER_CTRL_PW4_ENABLE (1 <<  8)
+#define CMD_DISP_POWER_CTRL_PM0_ENABLE (1 << 16)
+#define CMD_DISP_POWER_CTRL_PM1_ENABLE (1 << 18)
+
+#define CMD_INT_STATUS 0x037
+#define CMD_INT_MASK 0x038
+#define CMD_INT_ENABLE 0x039
+#define CMD_INT_TYPE 0x03a
+#define CMD_INT_POLARITY 0x03b
+#define INT_FRAME_END (1 << 1)
+#define INT_VBLANK (1 << 2)
+#define INT_WIN_A_UF (1 << 8)
+#define INT_WIN_B_UF (1 << 9)
+#define INT_WIN_C_UF (1 << 10)
+#define INT_WIN_A_OF (1 << 14)
+#define INT_WIN_B_OF (1 << 15)
+#define INT_WIN_C_OF (1 << 16)
+
+#define COM_PIN_OUTPUT_ENABLE(x) (0x302 + (x))
+#define COM_PIN_OUTPUT_POLARITY(x) (0x306 + (x))
+#define COM_PIN_OUTPUT_DATA(x) (0x30a + (x))
+#define COM_PIN_OUTPUT_SEL(x) (0x314 + (x))
+
+#define COM_PIN_OUTPUT_POLARITY_PIN1_LVS_OUTPUT (1 << 28)
+#define COM_PIN_OUTPUT_POLARITY_PIN1_LHS_OUTPUT (1 << 30)
+
+#define DISP_MEM_HIGH_PRI 0x403
+#define DISP_MEM_HIGH_PRI_TIMER 0x404
+#define DISP_TIMING_OPT 0x405
+#define DISP_REF_TO_SYNC 0x406
+#define DISP_SYNC_WIDTH 0x407
+#define DISP_BACK_PORCH 0x408
+#define DISP_ACTIVE 0x409
+#define DISP_FRONT_PORCH 0x40a
+
+#define DISP_DATA_ENABLE_OPT 0x432
+#define DISP_DATA_ENABLE_OPT_SELECT_ACTIVE_BLANK (0 << 0)
+#define DISP_DATA_ENABLE_OPT_SELECT_ACTIVE (1 << 0)
+#define DISP_DATA_ENABLE_OPT_SELECT_ACTIVE_IS (2 << 0)
+#define DISP_DATA_ENABLE_OPT_CONTROL_ONECLK (0 << 2)
+#define DISP_DATA_ENABLE_OPT_CONTROL_NORMAL (1 << 2)
+#define DISP_DATA_ENABLE_OPT_CONTROL_EARLY_EXT (2 << 2)
+#define DISP_DATA_ENABLE_OPT_CONTROL_EARLY (3 << 2)
+#define DISP_DATA_ENABLE_OPT_CONTROL_ACTIVE_BLANK (4 << 2)
+
+#define DISP_INTERFACE_CTRL 0x42f
+#define DISP_INTERFACE_CTRL_DATA_FORMAT_DF1P1C (0 << 0)
+#define DISP_INTERFACE_CTRL_DATA_FORMAT_DF1P2C24B (1 << 0)
+#define DISP_INTERFACE_CTRL_DATA_FORMAT_DF1P2C18B (2 << 0)
+#define DISP_INTERFACE_CTRL_DATA_FORMAT_DF1P2C16B (3 << 0)
+#define DISP_INTERFACE_CTRL_DATA_FORMAT_DF2S (4 << 0)
+#define DISP_INTERFACE_CTRL_DATA_FORMAT_DF3S (5 << 0)
+#define DISP_INTERFACE_CTRL_DATA_FORMAT_DFSPI (6 << 0)
+#define DISP_INTERFACE_CTRL_DATA_FORMAT_DF1P3C24B (7 << 0)
+#define DISP_INTERFACE_CTRL_DATA_FORMAT_DF1P3C18B (8 << 0)
+#define DISP_INTERFACE_CTRL_ALIGN_MSB (0 << 8)
+#define DISP_INTERFACE_CTRL_ALIGN_LSB (1 << 8)
+#define DISP_INTERFACE_CTRL_ORDER_RED_BLUE (0 << 9)
+#define DISP_INTERFACE_CTRL_ORDER_BLUE_RED (1 << 9)
+
+#define DISP_SHIFT_CLK_OPT 0x431
+
+#define DISP_CLK_CTRL 0x42e
+#define DISP_CLK_CTRL_PIXEL_CLK_DIV_PCD1 (0 << 8)
+#define DISP_CLK_CTRL_PIXEL_CLK_DIV_PCD1H (1 << 8)
+#define DISP_CLK_CTRL_PIXEL_CLK_DIV_PCD2 (2 << 8)
+#define DISP_CLK_CTRL_PIXEL_CLK_DIV_PCD3 (3 << 8)
+#define DISP_CLK_CTRL_PIXEL_CLK_DIV_PCD4 (4 << 8)
+#define DISP_CLK_CTRL_PIXEL_CLK_DIV_PCD6 (5 << 8)
+#define DISP_CLK_CTRL_PIXEL_CLK_DIV_PCD8 (6 << 8)
+#define DISP_CLK_CTRL_PIXEL_CLK_DIV_PCD9 (7 << 8)
+#define DISP_CLK_CTRL_PIXEL_CLK_DIV_PCD12 (8 << 8)
+#define DISP_CLK_CTRL_PIXEL_CLK_DIV_PCD16 (9 << 8)
+#define DISP_CLK_CTRL_PIXEL_CLK_DIV_PCD18 (10 << 8)
+#define DISP_CLK_CTRL_PIXEL_CLK_DIV_PCD24 (11 << 8)
+#define DISP_CLK_CTRL_PIXEL_CLK_DIV_PCD13 (12 << 8)
+#define DISP_CLK_CTRL_CLK_DIV(x) ((x) & 0xff)
+
+#define CMD_WIN_HEADER 0x042
+#define CMD_WIN_HEADER_WINDOW_A_SELECT (1 << 4)
+#define CMD_WIN_HEADER_WINDOW_B_SELECT (1 << 5)
+#define CMD_WIN_HEADER_WINDOW_C_SELECT (1 << 6)
+
+#define WIN_COLOR_DEPTH 0x703
+#define WIN_COLOR_DEPTH_P1 0
+#define WIN_COLOR_DEPTH_P2 1
+#define WIN_COLOR_DEPTH_P4 2
+#define WIN_COLOR_DEPTH_P8 3
+#define WIN_COLOR_DEPTH_B4G4R4A4 4
+#define WIN_COLOR_DEPTH_B5G5R5A 5
+#define WIN_COLOR_DEPTH_B5G6R5 6
+#define WIN_COLOR_DEPTH_AB5G5R5 7
+#define WIN_COLOR_DEPTH_B8G8R8A8 12
+#define WIN_COLOR_DEPTH_R8G8B8A8 13
+#define WIN_COLOR_DEPTH_B6x2G6x2R6x2A8 14
+#define WIN_COLOR_DEPTH_R6x2G6x2B6x2A8 15
+#define WIN_COLOR_DEPTH_YCbCr422 16
+#define WIN_COLOR_DEPTH_YUV422 17
+#define WIN_COLOR_DEPTH_YCbCr420P 18
+#define WIN_COLOR_DEPTH_YUV420P 19
+#define WIN_COLOR_DEPTH_YCbCr422P 20
+#define WIN_COLOR_DEPTH_YUV422P 21
+#define WIN_COLOR_DEPTH_YCbCr422R 22
+#define WIN_COLOR_DEPTH_YUV422R 23
+#define WIN_COLOR_DEPTH_YCbCr422RA 24
+#define WIN_COLOR_DEPTH_YUV422RA 25
+
+#define WIN_BYTE_SWAP 0x701
+#define WIN_BYTE_SWAP_NOSWAP (0 << 0)
+#define WIN_BYTE_SWAP_SWAP2 (1 << 0)
+#define WIN_BYTE_SWAP_SWAP4 (2 << 0)
+#define WIN_BYTE_SWAP_SWAP4HW (3 << 0)
+
+#define WIN_POSITION 0x704
+#define WIN_POSITION_H(x) (((x) & 0x1fff) <<  0)
+#define WIN_POSITION_V(x) (((x) & 0x1fff) << 16)
+
+#define WIN_SIZE 0x705
+#define WIN_SIZE_H(x) (((x) & 0x1fff) <<  0)
+#define WIN_SIZE_V(x) (((x) & 0x1fff) << 16)
+
+#define WIN_PRESCALED_SIZE 0x706
+#define WIN_PRESCALED_SIZE_H(x) (((x) & 0x7fff) <<  0)
+#define WIN_PRESCALED_SIZE_V(x) (((x) & 0x1fff) << 16)
+
+#define WIN_H_INITIAL_DDA 0x707
+#define WIN_V_INITIAL_DDA 0x708
+
+#define WIN_DDA_INC 0x709
+#define WIN_DDA_INC_H(x) (((x) & 0xffff) <<  0)
+#define WIN_DDA_INC_V(x) (((x) & 0xffff) << 16)
+
+#define WIN_LINE_STRIDE 0x70a
+#define WIN_BUF_STRIDE 0x70b
+#define WIN_UV_BUF_STRIDE 0x70c
+
+#define WIN_OPT 0x700
+#define WIN_OPT_COLOR_EXPAND (1 << 6)
+#define WIN_OPT_ENABLE (1 << 30)
+
+#define WINBUF_START_ADDR 0x800
+#define WINBUF_ADDR_H_OFFSET 0x806
+#define WINBUF_ADDR_V_OFFSET 0x808
+
+#define WIN_BLEND_NOKEY 0x70f
+#define WIN_BLEND_1WIN 0x710
+
+#define CMD_STATE_CTRL 0x041
+#define CMD_STATE_CTRL_GENERAL_ACT_REQ	(1 <<  0)
+#define CMD_STATE_CTRL_WIN_A_ACT_REQ	(1 <<  1)
+#define CMD_STATE_CTRL_WIN_B_ACT_REQ	(1 <<  2)
+#define CMD_STATE_CTRL_WIN_C_ACT_REQ	(1 <<  3)
+#define CMD_STATE_CTRL_GENERAL_UPDATE	(1 <<  8)
+#define CMD_STATE_CTRL_WIN_A_UPDATE	(1 <<  9)
+#define CMD_STATE_CTRL_WIN_B_UPDATE	(1 << 10)
+#define CMD_STATE_CTRL_WIN_C_UPDATE	(1 << 11)
+
+/* synchronization points */
+#define SYNCPT_VBLANK0 26
+#define SYNCPT_VBLANK1 27
+
+#endif /* TEGRA_DRV_H */
diff --git a/include/drm/tegra_drm.h b/include/drm/tegra_drm.h
new file mode 100644
index 0000000..b3dd44a
--- /dev/null
+++ b/include/drm/tegra_drm.h
@@ -0,0 +1,44 @@
+#ifndef _TEGRA_DRM_H_
+#define _TEGRA_DRM_H_
+
+enum tegra_drm_panel_type {
+	TEGRA_DRM_PANEL_RGB,
+};
+
+struct tegra_drm_mode {
+	unsigned int pixel_clock;
+	unsigned int vrefresh;
+
+	unsigned int width;
+	unsigned int height;
+	unsigned int bpp;
+
+	unsigned int href_to_sync;
+	unsigned int hsync_width;
+	unsigned int hback_porch;
+	unsigned int hfront_porch;
+
+	unsigned int vref_to_sync;
+	unsigned int vsync_width;
+	unsigned int vback_porch;
+	unsigned int vfront_porch;
+};
+
+struct tegra_drm_panel {
+	enum tegra_drm_panel_type type;
+
+	/* physical size */
+	unsigned int width;
+	unsigned int height;
+
+	/* display modes */
+	struct tegra_drm_mode *modes;
+	unsigned int num_modes;
+};
+
+struct tegra_drm_platform_data {
+	struct tegra_drm_panel *panels;
+	unsigned int num_panels;
+};
+
+#endif
-- 
1.7.10



More information about the devicetree-discuss mailing list