diff mbox

[RFC] drm/lcdc: add TI LCD Controller DRM driver

Message ID 1355443446-8723-1-git-send-email-robdclark@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Rob Clark Dec. 14, 2012, 12:04 a.m. UTC
A simple DRM/KMS driver for the TI LCD Controller found in various
smaller TI parts (AM33xx, OMAPL138, etc).  This driver uses the
CMA helpers.  Currently only the TFP410 DVI encoder is supported
(tested with beaglebone + DVI cape).  There are also various LCD
displays, for which support can be added (as I get hw to test on),
and an external i2c HDMI encoder found on some boards.

The display controller supports a single CRTC.  And the encoder+
connector are split out into sub-devices.  Depending on which LCD
or external encoder is actually present, the appropriate output
module(s) will be loaded.

Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 drivers/gpu/drm/Kconfig            |   2 +
 drivers/gpu/drm/Makefile           |   1 +
 drivers/gpu/drm/lcdc/Kconfig       |  11 +
 drivers/gpu/drm/lcdc/Makefile      |   8 +
 drivers/gpu/drm/lcdc/lcdc_crtc.c   | 544 +++++++++++++++++++++++++++++++++
 drivers/gpu/drm/lcdc/lcdc_drv.c    | 604 +++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/lcdc/lcdc_drv.h    | 162 ++++++++++
 drivers/gpu/drm/lcdc/lcdc_regs.h   | 154 ++++++++++
 drivers/gpu/drm/lcdc/lcdc_tfp410.c | 424 ++++++++++++++++++++++++++
 drivers/gpu/drm/lcdc/lcdc_tfp410.h |  26 ++
 10 files changed, 1936 insertions(+)
 create mode 100644 drivers/gpu/drm/lcdc/Kconfig
 create mode 100644 drivers/gpu/drm/lcdc/Makefile
 create mode 100644 drivers/gpu/drm/lcdc/lcdc_crtc.c
 create mode 100644 drivers/gpu/drm/lcdc/lcdc_drv.c
 create mode 100644 drivers/gpu/drm/lcdc/lcdc_drv.h
 create mode 100644 drivers/gpu/drm/lcdc/lcdc_regs.h
 create mode 100644 drivers/gpu/drm/lcdc/lcdc_tfp410.c
 create mode 100644 drivers/gpu/drm/lcdc/lcdc_tfp410.h

Comments

Daniel Vetter Dec. 14, 2012, 8:50 p.m. UTC | #1
On Fri, Dec 14, 2012 at 1:04 AM, Rob Clark <robdclark@gmail.com> wrote:
> +static int lcdc_crtc_page_flip(struct drm_crtc *crtc,
> +               struct drm_framebuffer *fb,
> +               struct drm_pending_vblank_event *event)
> +{
> +       struct lcdc_crtc *lcdc_crtc = to_lcdc_crtc(crtc);
> +       struct drm_device *dev = crtc->dev;
> +
> +       if (lcdc_crtc->event) {
> +               dev_err(dev->dev, "already pending page flip!\n");
> +               return -EBUSY;
> +       }
> +
> +       // TODO we should hold a ref to the fb somewhere..

Note that with the current fb refcounting nothing prevents you from
fixing this. The ugly problems I've had to solve for the locking
rework are all due to the drm core (i.e. setcrtc/pageflip/...) ioctl
functions assuming that an fb can't suddenly disappear while holding
the mode_config lock. Since I wanted to remove that requirement I've
had to changed the refcounting in the drm core functions.

But drivers can already extend the lifetime of an fb simply by
grabbing a reference (as long as they grab that reference while
holding the struct mutex all the time between fb lookup and grabbing
the reference). And it will continue to work the same with the new
locking scheme.
-Daniel

> +       crtc->fb = fb;
> +       lcdc_crtc->event = event;
> +       update_scanout(crtc);
> +
> +       return 0;
> +}
> +
Tomi Valkeinen Dec. 17, 2012, 1:56 p.m. UTC | #2
On 2012-12-14 02:04, Rob Clark wrote:
> A simple DRM/KMS driver for the TI LCD Controller found in various
> smaller TI parts (AM33xx, OMAPL138, etc).  This driver uses the
> CMA helpers.  Currently only the TFP410 DVI encoder is supported
> (tested with beaglebone + DVI cape).  There are also various LCD
> displays, for which support can be added (as I get hw to test on),
> and an external i2c HDMI encoder found on some boards.
> 
> The display controller supports a single CRTC.  And the encoder+
> connector are split out into sub-devices.  Depending on which LCD
> or external encoder is actually present, the appropriate output
> module(s) will be loaded.
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>
> ---
>  drivers/gpu/drm/Kconfig            |   2 +
>  drivers/gpu/drm/Makefile           |   1 +
>  drivers/gpu/drm/lcdc/Kconfig       |  11 +
>  drivers/gpu/drm/lcdc/Makefile      |   8 +
>  drivers/gpu/drm/lcdc/lcdc_crtc.c   | 544 +++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/lcdc/lcdc_drv.c    | 604 +++++++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/lcdc/lcdc_drv.h    | 162 ++++++++++
>  drivers/gpu/drm/lcdc/lcdc_regs.h   | 154 ++++++++++
>  drivers/gpu/drm/lcdc/lcdc_tfp410.c | 424 ++++++++++++++++++++++++++
>  drivers/gpu/drm/lcdc/lcdc_tfp410.h |  26 ++
>  10 files changed, 1936 insertions(+)
>  create mode 100644 drivers/gpu/drm/lcdc/Kconfig
>  create mode 100644 drivers/gpu/drm/lcdc/Makefile
>  create mode 100644 drivers/gpu/drm/lcdc/lcdc_crtc.c
>  create mode 100644 drivers/gpu/drm/lcdc/lcdc_drv.c
>  create mode 100644 drivers/gpu/drm/lcdc/lcdc_drv.h
>  create mode 100644 drivers/gpu/drm/lcdc/lcdc_regs.h
>  create mode 100644 drivers/gpu/drm/lcdc/lcdc_tfp410.c
>  create mode 100644 drivers/gpu/drm/lcdc/lcdc_tfp410.h

"lcdc" is quite a common term. The directory should perhaps be something
like ti-lcdc?

Probably not relevant, but I wonder if the same LCDC was used in
omap1... It'd be nice to get rid of the omap1 fb driver.

I'm not very enthusiastic about adding ti-lcdc specific panel/chip
drivers. It's not really a big deal if it's only kernel code, but you
add device-tree bindings also, which is an external API that you need to
support after adding it.

I'd rather see the energy put to common display framework, and get this
whole panel/chip driver issue solved in a generic manner.

 Tomi
Rob Clark Dec. 17, 2012, 2:39 p.m. UTC | #3
On Mon, Dec 17, 2012 at 7:56 AM, Tomi Valkeinen <tomi.valkeinen@ti.com> wrote:
> On 2012-12-14 02:04, Rob Clark wrote:
>> A simple DRM/KMS driver for the TI LCD Controller found in various
>> smaller TI parts (AM33xx, OMAPL138, etc).  This driver uses the
>> CMA helpers.  Currently only the TFP410 DVI encoder is supported
>> (tested with beaglebone + DVI cape).  There are also various LCD
>> displays, for which support can be added (as I get hw to test on),
>> and an external i2c HDMI encoder found on some boards.
>>
>> The display controller supports a single CRTC.  And the encoder+
>> connector are split out into sub-devices.  Depending on which LCD
>> or external encoder is actually present, the appropriate output
>> module(s) will be loaded.
>>
>> Signed-off-by: Rob Clark <robdclark@gmail.com>
>> ---
>>  drivers/gpu/drm/Kconfig            |   2 +
>>  drivers/gpu/drm/Makefile           |   1 +
>>  drivers/gpu/drm/lcdc/Kconfig       |  11 +
>>  drivers/gpu/drm/lcdc/Makefile      |   8 +
>>  drivers/gpu/drm/lcdc/lcdc_crtc.c   | 544 +++++++++++++++++++++++++++++++++
>>  drivers/gpu/drm/lcdc/lcdc_drv.c    | 604 +++++++++++++++++++++++++++++++++++++
>>  drivers/gpu/drm/lcdc/lcdc_drv.h    | 162 ++++++++++
>>  drivers/gpu/drm/lcdc/lcdc_regs.h   | 154 ++++++++++
>>  drivers/gpu/drm/lcdc/lcdc_tfp410.c | 424 ++++++++++++++++++++++++++
>>  drivers/gpu/drm/lcdc/lcdc_tfp410.h |  26 ++
>>  10 files changed, 1936 insertions(+)
>>  create mode 100644 drivers/gpu/drm/lcdc/Kconfig
>>  create mode 100644 drivers/gpu/drm/lcdc/Makefile
>>  create mode 100644 drivers/gpu/drm/lcdc/lcdc_crtc.c
>>  create mode 100644 drivers/gpu/drm/lcdc/lcdc_drv.c
>>  create mode 100644 drivers/gpu/drm/lcdc/lcdc_drv.h
>>  create mode 100644 drivers/gpu/drm/lcdc/lcdc_regs.h
>>  create mode 100644 drivers/gpu/drm/lcdc/lcdc_tfp410.c
>>  create mode 100644 drivers/gpu/drm/lcdc/lcdc_tfp410.h
>
> "lcdc" is quite a common term. The directory should perhaps be something
> like ti-lcdc?

yeah.. but not one else was using it (other than internally to the
driver).. I guess we could call it tilcdc or ti-lcdc

> Probably not relevant, but I wonder if the same LCDC was used in
> omap1... It'd be nice to get rid of the omap1 fb driver.

would be interesting if you have any idea where to find hw to test
with (museum?)

> I'm not very enthusiastic about adding ti-lcdc specific panel/chip
> drivers. It's not really a big deal if it's only kernel code, but you
> add device-tree bindings also, which is an external API that you need to
> support after adding it.
>
> I'd rather see the energy put to common display framework, and get this
> whole panel/chip driver issue solved in a generic manner.

yeah, I was expecting to migrate to CDF once it exists, but needed
something for now.  I'm using the exercise to get my thoughts straight
on how CDF should fit into KMS.  (One thing I plan to add support for
is an i2c connected hdmi encoder.. which looks like it would fit well
in drivers/gpu/drm/i2c.. so the drm encoder-slave stuff might be the
way.)

If you have any suggestions on the DT bindings, I'd like to hear 'em.

BR,
-R

>  Tomi
>
>
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Rob Clark Dec. 17, 2012, 3:02 p.m. UTC | #4
On Mon, Dec 17, 2012 at 8:39 AM, Rob Clark <robdclark@gmail.com> wrote:
>> I'm not very enthusiastic about adding ti-lcdc specific panel/chip
>> drivers. It's not really a big deal if it's only kernel code, but you
>> add device-tree bindings also, which is an external API that you need to
>> support after adding it.
>>
>> I'd rather see the energy put to common display framework, and get this
>> whole panel/chip driver issue solved in a generic manner.
>
> yeah, I was expecting to migrate to CDF once it exists, but needed
> something for now.  I'm using the exercise to get my thoughts straight
> on how CDF should fit into KMS.  (One thing I plan to add support for
> is an i2c connected hdmi encoder.. which looks like it would fit well
> in drivers/gpu/drm/i2c.. so the drm encoder-slave stuff might be the
> way.)
>
> If you have any suggestions on the DT bindings, I'd like to hear 'em.

btw, a little bit of-topic, but speaking of DT...

Anybody have any clue about how backlight devices are supposed to work
in this brave new DT world?  In the old days, the board file would
stuff a fxn ptr to control backlight in pdata for the driver.  But we
don't have this any more.  I think I need some way to retrieve the
'struct backlight_device' ptr associated with the panel driver, so
that the appropriate dpms fxn ptrs can enable/disable the backlight.

I'm thinking the dt file should have something that looks roughly like:

                /* Settings for CDTech_S035Q01 / LCD3 cape: */
                panel {
                        compatible = "lcdc,panel";
                        pinctrl-names = "default";
                        pinctrl-0 = <&bone_lcd3_cape_lcd_pins>;
                        panel-info {
                                ac-bias           = <255>;
                                ac-bias-intrpt    = <0>;
                                dma-burst-sz      = <16>;
                                bpp               = <16>;
                                fdd               = <0x80>;
                                tft-alt-mode      = <0>;
                                stn-565-mode      = <0>;
                                mono-8bit-mode    = <0>;
                                invert-line-clock = <1>;
                                invert-frm-clock  = <1>;
                                sync-edge         = <0>;
                                sync-ctrl         = <1>;
                                raster-order      = <0>;
                                fifo-th           = <0>;
                        };
                        display-timings {
                                native-mode = <&timing0>;
                                timing0: 320x240 {
                                        hactive         = <320>;
                                        vactive         = <240>;
                                        hback-porch     = <21>;
                                        hfront-porch    = <58>;
                                        hsync-len       = <47>;
                                        vback-porch     = <11>;
                                        vfront-porch    = <23>;
                                        vsync-len       = <2>;
                                        clock-frequency = <8000000>;
                                };
                        };

                        backlight {
                                compatible = "tps65217-backlight";
                                isel = <1>;
                                fdim = <200>;

                                tps = <&tps>;   /* link to the tps */
                                brightness = <100>;
                        };
                };

display-timings is based on the of-videomode helpers patch..
panel-info probably needs to be made to be something more generic, but
we need something to know how to configure the crtc properly..

but I'm not quite sure what to do with the backlight..

BR,
-R
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Rob Clark Dec. 17, 2012, 4:36 p.m. UTC | #5
On Mon, Dec 17, 2012 at 9:26 AM, Sekhar Nori <nori.sekhar@gmail.com> wrote:
> Hi Rob,
>
>
> On Monday, December 17, 2012, Rob Clark wrote:
>>
>> On Mon, Dec 17, 2012 at 8:39 AM, Rob Clark <robdclark@gmail.com> wrote:
>> >> I'm not very enthusiastic about adding ti-lcdc specific panel/chip
>> >> drivers. It's not really a big deal if it's only kernel code, but you
>> >> add device-tree bindings also, which is an external API that you need
>> >> to
>> >> support after adding it.
>> >>
>> >> I'd rather see the energy put to common display framework, and get this
>> >> whole panel/chip driver issue solved in a generic manner.
>> >
>> > yeah, I was expecting to migrate to CDF once it exists, but needed
>> > something for now.  I'm using the exercise to get my thoughts straight
>> > on how CDF should fit into KMS.  (One thing I plan to add support for
>> > is an i2c connected hdmi encoder.. which looks like it would fit well
>> > in drivers/gpu/drm/i2c.. so the drm encoder-slave stuff might be the
>> > way.)
>> >
>> > If you have any suggestions on the DT bindings, I'd like to hear 'em.
>>
>> btw, a little bit of-topic, but speaking of DT...
>>
>> Anybody have any clue about how backlight devices are supposed to work
>> in this brave new DT world?
>
>
> See Runtime interpreted power sequences here:
>  http://lkml.indiana.edu/hypermail/linux/kernel/1208.2/00029.html
>
> It is an attempt to address this need.

hmm, I'm not really sure that is what is needed..  or rather, it might
perhaps make sense to have a generic backlight driver implementation
that could be used where appropriate, but I'm a bit suspicious about
that trying to cover absolutely everything.

From the drm/display driver we don't even want to care how the
backlight is implemented.  You could have (just making something up
hypothetically) a backlight controlled via a uart or some sort of
other crazy magic.. and eventually the generic interpreter gets out of
hand.

Really I think we just want a way to retrieve a 'struct
backlight_device *' that is created somewhere else.  We don't care how
that backlight driver is implemented.  I don't think we want an
interpreter.. we want a way to lookup backlight device by name or
phandle or something like that.

BR,
-R
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Thierry Reding Dec. 30, 2012, 9:48 a.m. UTC | #6
On Mon, Dec 17, 2012 at 10:36:10AM -0600, Rob Clark wrote:
> On Mon, Dec 17, 2012 at 9:26 AM, Sekhar Nori <nori.sekhar@gmail.com> wrote:
> > Hi Rob,
> >
> >
> > On Monday, December 17, 2012, Rob Clark wrote:
> >>
> >> On Mon, Dec 17, 2012 at 8:39 AM, Rob Clark <robdclark@gmail.com> wrote:
> >> >> I'm not very enthusiastic about adding ti-lcdc specific panel/chip
> >> >> drivers. It's not really a big deal if it's only kernel code, but you
> >> >> add device-tree bindings also, which is an external API that you need
> >> >> to
> >> >> support after adding it.
> >> >>
> >> >> I'd rather see the energy put to common display framework, and get this
> >> >> whole panel/chip driver issue solved in a generic manner.
> >> >
> >> > yeah, I was expecting to migrate to CDF once it exists, but needed
> >> > something for now.  I'm using the exercise to get my thoughts straight
> >> > on how CDF should fit into KMS.  (One thing I plan to add support for
> >> > is an i2c connected hdmi encoder.. which looks like it would fit well
> >> > in drivers/gpu/drm/i2c.. so the drm encoder-slave stuff might be the
> >> > way.)
> >> >
> >> > If you have any suggestions on the DT bindings, I'd like to hear 'em.
> >>
> >> btw, a little bit of-topic, but speaking of DT...
> >>
> >> Anybody have any clue about how backlight devices are supposed to work
> >> in this brave new DT world?
> >
> >
> > See Runtime interpreted power sequences here:
> >  http://lkml.indiana.edu/hypermail/linux/kernel/1208.2/00029.html
> >
> > It is an attempt to address this need.
> 
> hmm, I'm not really sure that is what is needed..  or rather, it might
> perhaps make sense to have a generic backlight driver implementation
> that could be used where appropriate, but I'm a bit suspicious about
> that trying to cover absolutely everything.
> 
> >From the drm/display driver we don't even want to care how the
> backlight is implemented.  You could have (just making something up
> hypothetically) a backlight controlled via a uart or some sort of
> other crazy magic.. and eventually the generic interpreter gets out of
> hand.
> 
> Really I think we just want a way to retrieve a 'struct
> backlight_device *' that is created somewhere else.  We don't care how
> that backlight driver is implemented.  I don't think we want an
> interpreter.. we want a way to lookup backlight device by name or
> phandle or something like that.

I posted a patch for this a while back. It adds a new function called
of_find_backlight_by_node() that does exactly that. The way it is
supposed to be used is somewhat like this:

	panel {
		backlight = <&backlight>;
	};

	backlight: backlight {
		...
	};

Then look up the phandle in the panel/DRM driver and call the new
function on the corresponding struct device_node *.

The patch went into 3.8-rc1.

And by the way, the future of the power-sequences series isn't very
clear. It was initially developed to allow the DT to contain power
sequencing for panels as part of the pwm-backlight driver, but that
idea was more or less killed by the CDF. The latest I know of is that
they could still be used as part of CDF to implement the actual power
sequences for drivers but not use their DT representation because
several people were unhappy about it.

Thierry
diff mbox

Patch

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 983201b..255efcb 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -212,3 +212,5 @@  source "drivers/gpu/drm/cirrus/Kconfig"
 source "drivers/gpu/drm/shmobile/Kconfig"
 
 source "drivers/gpu/drm/tegra/Kconfig"
+
+source "drivers/gpu/drm/lcdc/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 6f58c81..8e0a19a 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -50,4 +50,5 @@  obj-$(CONFIG_DRM_UDL) += udl/
 obj-$(CONFIG_DRM_AST) += ast/
 obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
 obj-$(CONFIG_DRM_TEGRA) += tegra/
+obj-$(CONFIG_DRM_LCDC)	+= lcdc/
 obj-y			+= i2c/
diff --git a/drivers/gpu/drm/lcdc/Kconfig b/drivers/gpu/drm/lcdc/Kconfig
new file mode 100644
index 0000000..084b0a0
--- /dev/null
+++ b/drivers/gpu/drm/lcdc/Kconfig
@@ -0,0 +1,11 @@ 
+config DRM_LCDC
+	tristate "DRM Support for TI LCDC Display Controller"
+	depends on DRM && OF
+	select DRM_KMS_HELPER
+	select DRM_KMS_CMA_HELPER
+	select DRM_GEM_CMA_HELPER
+	help
+	  Choose this option if you have an TI SoC with LCDC display
+	  controller, for example AM33xx in beagle-bone, DA8xx, or
+	  OMAP-L1xx.  This driver replaces the FB_DA8XX fbdev driver.
+
diff --git a/drivers/gpu/drm/lcdc/Makefile b/drivers/gpu/drm/lcdc/Makefile
new file mode 100644
index 0000000..db32161
--- /dev/null
+++ b/drivers/gpu/drm/lcdc/Makefile
@@ -0,0 +1,8 @@ 
+ccflags-y := -Iinclude/drm -Werror
+
+lcdc-y := \
+	lcdc_crtc.o \
+	lcdc_tfp410.o \
+	lcdc_drv.o
+
+obj-$(CONFIG_DRM_LCDC)	+= lcdc.o
diff --git a/drivers/gpu/drm/lcdc/lcdc_crtc.c b/drivers/gpu/drm/lcdc/lcdc_crtc.c
new file mode 100644
index 0000000..052c2c1
--- /dev/null
+++ b/drivers/gpu/drm/lcdc/lcdc_crtc.c
@@ -0,0 +1,544 @@ 
+/*
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "lcdc_drv.h"
+#include "lcdc_regs.h"
+
+struct lcdc_crtc {
+	struct drm_crtc base;
+
+	const struct lcdc_panel_info *info;
+	uint32_t dirty;
+	dma_addr_t start, end;
+	struct drm_pending_vblank_event *event;
+	int dpms;
+	wait_queue_head_t frame_done_wq;
+	bool frame_done;
+};
+#define to_lcdc_crtc(x) container_of(x, struct lcdc_crtc, base)
+
+static void set_scanout(struct drm_crtc *crtc, uint32_t stat)
+{
+	struct lcdc_crtc *lcdc_crtc = to_lcdc_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+
+	pm_runtime_get_sync(dev->dev);
+	if (stat & LCDC_END_OF_FRAME0) {
+		lcdc_write(dev, LCDC_DMA_FRM_BUF_BASE_ADDR_0_REG,
+				lcdc_crtc->start);
+		lcdc_write(dev, LCDC_DMA_FRM_BUF_CEILING_ADDR_0_REG,
+				lcdc_crtc->end);
+		lcdc_crtc->dirty &= ~LCDC_END_OF_FRAME0;
+	}
+
+	if (stat & LCDC_END_OF_FRAME1) {
+		lcdc_write(dev, LCDC_DMA_FRM_BUF_BASE_ADDR_1_REG,
+				lcdc_crtc->start);
+		lcdc_write(dev, LCDC_DMA_FRM_BUF_CEILING_ADDR_1_REG,
+				lcdc_crtc->end);
+		lcdc_crtc->dirty &= ~LCDC_END_OF_FRAME1;
+	}
+	pm_runtime_put_sync(dev->dev);
+}
+
+static void update_scanout(struct drm_crtc *crtc)
+{
+	struct lcdc_crtc *lcdc_crtc = to_lcdc_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+	struct drm_framebuffer *fb = crtc->fb;
+	struct drm_gem_cma_object *gem;
+	unsigned int depth, bpp;
+
+	drm_fb_get_bpp_depth(fb->pixel_format, &depth, &bpp);
+	gem = drm_fb_cma_get_gem_obj(fb, 0);
+
+	lcdc_crtc->start = gem->paddr + fb->offsets[0] +
+			(crtc->y * fb->pitches[0]) + (crtc->x * bpp/8);
+
+	lcdc_crtc->end = lcdc_crtc->start +
+			(crtc->mode.vdisplay * fb->pitches[0]);
+
+	if (lcdc_crtc->dpms == DRM_MODE_DPMS_ON) {
+		/* already enabled, so just mark the frames that need
+		 * updating and they will be updated on vblank:
+		 */
+		lcdc_crtc->dirty |= LCDC_END_OF_FRAME0 | LCDC_END_OF_FRAME1;
+		drm_vblank_get(dev, 0);
+	} else {
+		/* not enabled yet, so update registers immediately: */
+		set_scanout(crtc, LCDC_END_OF_FRAME0 | LCDC_END_OF_FRAME1);
+	}
+}
+
+static void start(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	struct lcdc_drm_private *priv = dev->dev_private;
+
+	if (priv->rev == 2) {
+		lcdc_set(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET);
+		msleep(1);
+		lcdc_clear(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET);
+		msleep(1);
+	}
+
+	lcdc_set(dev, LCDC_DMA_CTRL_REG, LCDC_DUAL_FRAME_BUFFER_ENABLE);
+	lcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_PALETTE_LOAD_MODE(DATA_ONLY));
+	lcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
+}
+
+static void stop(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+
+	lcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
+}
+
+static void lcdc_crtc_destroy(struct drm_crtc *crtc)
+{
+	struct lcdc_crtc *lcdc_crtc = to_lcdc_crtc(crtc);
+
+	WARN_ON(lcdc_crtc->dpms == DRM_MODE_DPMS_ON);
+
+	drm_crtc_cleanup(crtc);
+	kfree(lcdc_crtc);
+}
+
+static int lcdc_crtc_page_flip(struct drm_crtc *crtc,
+		struct drm_framebuffer *fb,
+		struct drm_pending_vblank_event *event)
+{
+	struct lcdc_crtc *lcdc_crtc = to_lcdc_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+
+	if (lcdc_crtc->event) {
+		dev_err(dev->dev, "already pending page flip!\n");
+		return -EBUSY;
+	}
+
+	// TODO we should hold a ref to the fb somewhere..
+	crtc->fb = fb;
+	lcdc_crtc->event = event;
+	update_scanout(crtc);
+
+	return 0;
+}
+
+static void lcdc_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+	struct lcdc_crtc *lcdc_crtc = to_lcdc_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+	struct lcdc_drm_private *priv = dev->dev_private;
+
+	if (lcdc_crtc->dpms == mode)
+		return;
+
+	lcdc_crtc->dpms = mode;
+
+	if (mode == DRM_MODE_DPMS_ON) {
+		pm_runtime_get_sync(dev->dev);
+		start(crtc);
+	} else {
+		lcdc_crtc->frame_done = false;
+		stop(crtc);
+
+		/* if necessary wait for framedone irq which will still come
+		 * before putting things to sleep..
+		 */
+		if (priv->rev == 2) {
+			int ret = wait_event_timeout(
+					lcdc_crtc->frame_done_wq,
+					lcdc_crtc->frame_done,
+					msecs_to_jiffies(50));
+			if (ret == 0)
+				dev_err(dev->dev, "timeout waiting for framedone\n");
+		}
+
+		pm_runtime_put_sync(dev->dev);
+	}
+}
+
+static bool lcdc_crtc_mode_fixup(struct drm_crtc *crtc,
+		const struct drm_display_mode *mode,
+		struct drm_display_mode *adjusted_mode)
+{
+	return true;
+}
+
+static void lcdc_crtc_prepare(struct drm_crtc *crtc)
+{
+	lcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
+}
+
+static void lcdc_crtc_commit(struct drm_crtc *crtc)
+{
+	lcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
+}
+
+static int lcdc_crtc_mode_set(struct drm_crtc *crtc,
+		struct drm_display_mode *mode,
+		struct drm_display_mode *adjusted_mode,
+		int x, int y,
+		struct drm_framebuffer *old_fb)
+{
+	struct lcdc_crtc *lcdc_crtc = to_lcdc_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+	struct lcdc_drm_private *priv = dev->dev_private;
+	const struct lcdc_panel_info *info = lcdc_crtc->info;
+	uint32_t reg, hbp, hfp, hsw, vbp, vfp, vsw;
+	int ret;
+
+	ret = lcdc_crtc_mode_valid(crtc, mode);
+	if (WARN_ON(ret))
+		return ret;
+
+	if (WARN_ON(!info))
+		return -EINVAL;
+
+	pm_runtime_get_sync(dev->dev);
+
+	/* Configure the Burst Size and fifo threshold of DMA: */
+	reg = lcdc_read(dev, LCDC_DMA_CTRL_REG) & ~0x00000770;
+	switch (info->dma_burst_sz) {
+	case 1:
+		reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_1);
+		break;
+	case 2:
+		reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_2);
+		break;
+	case 4:
+		reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_4);
+		break;
+	case 8:
+		reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_8);
+		break;
+	case 16:
+		reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_16);
+		break;
+	default:
+		return -EINVAL;
+	}
+	reg |= (info->fifo_th << 8);
+	lcdc_write(dev, LCDC_DMA_CTRL_REG, reg);
+
+	/* Configure the AC Bias Period and Number of Transitions per Interrupt: */
+	reg = lcdc_read(dev, LCDC_RASTER_TIMING_2_REG) & ~0x000fff00;
+	reg |= LCDC_AC_BIAS_FREQUENCY(info->ac_bias) |
+		LCDC_AC_BIAS_TRANSITIONS_PER_INT(info->ac_bias_intrpt);
+	lcdc_write(dev, LCDC_RASTER_TIMING_2_REG, reg);
+
+	/* Configure timings: */
+	hbp = mode->htotal - mode->hsync_end;
+	hfp = mode->hsync_start - mode->hdisplay;
+	hsw = mode->hsync_end - mode->hsync_start;
+	vbp = mode->vtotal - mode->vsync_end;
+	vfp = mode->vsync_start - mode->vdisplay;
+	vsw = mode->vsync_end - mode->vsync_start;
+	reg = (((mode->hdisplay >> 4) - 1) << 4) |
+		((hbp & 0xff) << 24) |
+		((hfp & 0xff) << 16) |
+		((hsw & 0x3f) << 10);
+	if (priv->rev == 2)
+		reg |= (((mode->hdisplay >> 4) - 1) & 0x40) >> 3;
+	lcdc_write(dev, LCDC_RASTER_TIMING_0_REG, reg);
+
+	reg = ((mode->vdisplay - 1) & 0x3ff) |
+		((vbp & 0xff) << 24) |
+		((vfp & 0xff) << 16) |
+		((vsw & 0x3f) << 10);
+	lcdc_write(dev, LCDC_RASTER_TIMING_1_REG, reg);
+
+	/* Configure display type: */
+	reg = lcdc_read(dev, LCDC_RASTER_CTRL_REG) &
+		~(LCDC_TFT_MODE | LCDC_MONO_8BIT_MODE | LCDC_MONOCHROME_MODE |
+			LCDC_V2_TFT_24BPP_MODE | LCDC_V2_TFT_24BPP_UNPACK | 0x000ff000);
+	reg |= LCDC_TFT_MODE; /* no monochrome/passive support */
+	if (info->tft_alt_mode)
+		reg |= LCDC_TFT_ALT_ENABLE;
+	if (priv->rev == 2) {
+		unsigned int depth, bpp;
+
+		drm_fb_get_bpp_depth(crtc->fb->pixel_format, &depth, &bpp);
+		switch (bpp) {
+		case 16:
+			break;
+		case 32:
+			reg |= LCDC_V2_TFT_24BPP_UNPACK;
+			/* fallthrough */
+		case 24:
+			reg |= LCDC_V2_TFT_24BPP_MODE;
+			break;
+		default:
+			dev_err(dev->dev, "invalid pixel format\n");
+			return -EINVAL;
+		}
+	}
+	reg |= info->fdd < 12;
+	lcdc_write(dev, LCDC_RASTER_CTRL_REG, reg);
+
+	if (info->invert_pxl_clk)
+		lcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK);
+	else
+		lcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK);
+
+	if (info->sync_ctrl)
+		lcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL);
+	else
+		lcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL);
+
+	if (info->sync_edge)
+		lcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE);
+	else
+		lcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE);
+
+	if (info->invert_line_clock)
+		lcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_LINE_CLOCK);
+	else
+		lcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_LINE_CLOCK);
+
+	if (info->invert_frm_clock)
+		lcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_FRAME_CLOCK);
+	else
+		lcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_FRAME_CLOCK);
+
+	if (info->raster_order)
+		lcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER);
+	else
+		lcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER);
+
+
+	update_scanout(crtc);
+	lcdc_crtc_update_clk(crtc);
+
+	pm_runtime_put_sync(dev->dev);
+
+	return 0;
+}
+
+static int lcdc_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+		struct drm_framebuffer *old_fb)
+{
+	update_scanout(crtc);
+	return 0;
+}
+
+static void lcdc_crtc_load_lut(struct drm_crtc *crtc)
+{
+	/* TODO */
+}
+
+static const struct drm_crtc_funcs lcdc_crtc_funcs = {
+		.destroy        = lcdc_crtc_destroy,
+		.set_config     = drm_crtc_helper_set_config,
+		.page_flip      = lcdc_crtc_page_flip,
+};
+
+static const struct drm_crtc_helper_funcs lcdc_crtc_helper_funcs = {
+		.dpms           = lcdc_crtc_dpms,
+		.mode_fixup     = lcdc_crtc_mode_fixup,
+		.prepare        = lcdc_crtc_prepare,
+		.commit         = lcdc_crtc_commit,
+		.mode_set       = lcdc_crtc_mode_set,
+		.mode_set_base  = lcdc_crtc_mode_set_base,
+		.load_lut       = lcdc_crtc_load_lut,
+};
+
+int lcdc_crtc_max_width(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	struct lcdc_drm_private *priv = dev->dev_private;
+	int max_width = 0;
+
+	if (priv->rev == 1)
+		max_width = 1024;
+	else if (priv->rev == 2)
+		max_width = 2048;
+
+	return max_width;
+}
+
+int lcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode)
+{
+	if (mode->hdisplay > lcdc_crtc_max_width(crtc))
+		return MODE_VIRTUAL_X;
+
+	/* width must be multiple of 16 */
+	if (mode->hdisplay & 0xf)
+		return MODE_VIRTUAL_X;
+
+	if (mode->vdisplay > 2048)
+		return MODE_VIRTUAL_Y;
+
+	return MODE_OK;
+}
+
+void lcdc_crtc_set_panel_info(struct drm_crtc *crtc,
+		const struct lcdc_panel_info *info)
+{
+	struct lcdc_crtc *lcdc_crtc = to_lcdc_crtc(crtc);
+	lcdc_crtc->info = info;
+}
+
+void lcdc_crtc_update_clk(struct drm_crtc *crtc)
+{
+	struct lcdc_crtc *lcdc_crtc = to_lcdc_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+	struct lcdc_drm_private *priv = dev->dev_private;
+	int dpms = lcdc_crtc->dpms;
+	unsigned int lcd_clk, div;
+	int ret;
+
+	pm_runtime_get_sync(dev->dev);
+
+	if (dpms == DRM_MODE_DPMS_ON)
+		lcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
+
+	/* in raster mode, minimum divisor is 2: */
+	ret = clk_set_rate(priv->disp_clk, crtc->mode.clock * 1000 * 2);
+	if (ret) {
+		dev_err(dev->dev, "failed to set display clock rate to: %d\n",
+				crtc->mode.clock);
+		goto out;
+	}
+
+	lcd_clk = clk_get_rate(priv->clk);
+	div = lcd_clk / (crtc->mode.clock * 1000);
+
+	DBG("lcd_clk=%u, mode clock=%d, div=%u", lcd_clk, crtc->mode.clock, div);
+	DBG("fck=%lu, dpll_disp_ck=%lu", clk_get_rate(priv->clk), clk_get_rate(priv->disp_clk));
+
+	/* Configure the LCD clock divisor. */
+	lcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) |
+			LCDC_RASTER_MODE);
+
+	if (priv->rev == 2)
+		lcdc_set(dev, LCDC_CLK_ENABLE_REG,
+				LCDC_V2_DMA_CLK_EN | LCDC_V2_LIDD_CLK_EN |
+				LCDC_V2_CORE_CLK_EN);
+
+	if (dpms == DRM_MODE_DPMS_ON)
+		lcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
+
+out:
+	pm_runtime_put_sync(dev->dev);
+}
+
+irqreturn_t lcdc_crtc_irq(struct drm_crtc *crtc)
+{
+	struct lcdc_crtc *lcdc_crtc = to_lcdc_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+	struct lcdc_drm_private *priv = dev->dev_private;
+	uint32_t stat = lcdc_read_irqstatus(dev);
+
+	if ((stat & LCDC_SYNC_LOST) && (stat & LCDC_FIFO_UNDERFLOW)) {
+		stop(crtc);
+		dev_err(dev->dev, "error: %08x\n", stat);
+		lcdc_clear_irqstatus(dev, stat);
+		start(crtc);
+	} else if (stat & LCDC_PL_LOAD_DONE) {
+		/* TODO lut load.. */
+		lcdc_clear_irqstatus(dev, stat);
+	} else {
+		struct drm_pending_vblank_event *event;
+		unsigned long flags;
+		uint32_t dirty = lcdc_crtc->dirty & stat;
+
+		lcdc_clear_irqstatus(dev, stat);
+
+		if (dirty)
+			set_scanout(crtc, dirty);
+
+		drm_handle_vblank(dev, 0);
+
+		spin_lock_irqsave(&dev->event_lock, flags);
+		event = lcdc_crtc->event;
+		lcdc_crtc->event = NULL;
+		if (event)
+			drm_send_vblank_event(dev, 0, event);
+		spin_unlock_irqrestore(&dev->event_lock, flags);
+
+		if (dirty && !lcdc_crtc->dirty)
+			drm_vblank_put(dev, 0);
+	}
+
+	if (priv->rev == 2) {
+		if (stat & LCDC_FRAME_DONE) {
+			lcdc_crtc->frame_done = true;
+			wake_up(&lcdc_crtc->frame_done_wq);
+		}
+		lcdc_write(dev, LCDC_END_OF_INT_IND_REG, 0);
+	}
+
+	return IRQ_HANDLED;
+}
+
+void lcdc_crtc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file)
+{
+	struct lcdc_crtc *lcdc_crtc = to_lcdc_crtc(crtc);
+	struct drm_pending_vblank_event *event;
+	struct drm_device *dev = crtc->dev;
+	unsigned long flags;
+
+	/* Destroy the pending vertical blanking event associated with the
+	 * pending page flip, if any, and disable vertical blanking interrupts.
+	 */
+	spin_lock_irqsave(&dev->event_lock, flags);
+	event = lcdc_crtc->event;
+	if (event && event->base.file_priv == file) {
+		lcdc_crtc->event = NULL;
+		event->base.destroy(&event->base);
+		drm_vblank_put(dev, 0);
+	}
+	spin_unlock_irqrestore(&dev->event_lock, flags);
+}
+
+struct drm_crtc *lcdc_crtc_create(struct drm_device *dev)
+{
+	struct lcdc_crtc *lcdc_crtc;
+	struct drm_crtc *crtc;
+	int ret;
+
+	lcdc_crtc = kzalloc(sizeof(*lcdc_crtc), GFP_KERNEL);
+	if (!lcdc_crtc) {
+		dev_err(dev->dev, "allocation failed\n");
+		return NULL;
+	}
+
+	lcdc_crtc->dpms = DRM_MODE_DPMS_OFF;
+	init_waitqueue_head(&lcdc_crtc->frame_done_wq);
+
+	crtc = &lcdc_crtc->base;
+
+	ret = drm_crtc_init(dev, crtc, &lcdc_crtc_funcs);
+	if (ret < 0)
+		goto fail;
+
+	drm_crtc_helper_add(crtc, &lcdc_crtc_helper_funcs);
+
+	return crtc;
+
+fail:
+	lcdc_crtc_destroy(crtc);
+	return NULL;
+}
diff --git a/drivers/gpu/drm/lcdc/lcdc_drv.c b/drivers/gpu/drm/lcdc/lcdc_drv.c
new file mode 100644
index 0000000..4f694a9
--- /dev/null
+++ b/drivers/gpu/drm/lcdc/lcdc_drv.c
@@ -0,0 +1,604 @@ 
+/*
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* LCDC DRM driver, based on da8xx-fb */
+
+#include "lcdc_drv.h"
+#include "lcdc_regs.h"
+#include "lcdc_tfp410.h"
+
+#include "drm_fb_helper.h"
+
+static LIST_HEAD(module_list);
+
+void lcdc_module_init(struct lcdc_module *mod, const char *name,
+		const struct lcdc_module_ops *funcs)
+{
+	mod->name = name;
+	mod->funcs = funcs;
+	INIT_LIST_HEAD(&mod->list);
+	list_add(&mod->list, &module_list);
+}
+
+void lcdc_module_cleanup(struct lcdc_module *mod)
+{
+	list_del(&mod->list);
+}
+
+static struct of_device_id lcdc_of_match[];
+
+static struct drm_framebuffer *lcdc_fb_create(struct drm_device *dev,
+		struct drm_file *file_priv, struct drm_mode_fb_cmd2 *mode_cmd)
+{
+	return drm_fb_cma_create(dev, file_priv, mode_cmd);
+}
+
+static void lcdc_fb_output_poll_changed(struct drm_device *dev)
+{
+	struct lcdc_drm_private *priv = dev->dev_private;
+	if (priv->fbdev)
+		drm_fbdev_cma_hotplug_event(priv->fbdev);
+}
+
+static const struct drm_mode_config_funcs mode_config_funcs = {
+	.fb_create = lcdc_fb_create,
+	.output_poll_changed = lcdc_fb_output_poll_changed,
+};
+
+static int modeset_init(struct drm_device *dev)
+{
+	struct lcdc_drm_private *priv = dev->dev_private;
+	struct lcdc_module *mod;
+
+	drm_mode_config_init(dev);
+
+	priv->crtc = lcdc_crtc_create(dev);
+
+	list_for_each_entry(mod, &module_list, list) {
+		DBG("loading module: %s", mod->name);
+		mod->funcs->modeset_init(mod, dev);
+	}
+
+	if ((priv->num_encoders = 0) || (priv->num_connectors == 0)) {
+		/* oh nos! */
+		dev_err(dev->dev, "no encoders/connectors found\n");
+		return -ENXIO;
+	}
+
+	dev->mode_config.min_width = 0;
+	dev->mode_config.min_height = 0;
+	dev->mode_config.max_width = lcdc_crtc_max_width(priv->crtc);
+	dev->mode_config.max_height = 2048;
+	dev->mode_config.funcs = &mode_config_funcs;
+
+	return 0;
+}
+
+#ifdef CONFIG_CPU_FREQ
+static int cpufreq_transition(struct notifier_block *nb,
+				     unsigned long val, void *data)
+{
+	struct lcdc_drm_private *priv = container_of(nb,
+			struct lcdc_drm_private, freq_transition);
+	if (val == CPUFREQ_POSTCHANGE) {
+		if (priv->lcd_fck_rate != clk_get_rate(priv->clk)) {
+			priv->lcd_fck_rate = clk_get_rate(priv->clk);
+			lcdc_crtc_update_clk(priv->crtc);
+		}
+	}
+
+	return 0;
+}
+#endif
+
+/*
+ * DRM operations:
+ */
+
+static int lcdc_unload(struct drm_device *dev)
+{
+	struct lcdc_drm_private *priv = dev->dev_private;
+	struct lcdc_module *mod;
+
+	drm_kms_helper_poll_fini(dev);
+	drm_mode_config_cleanup(dev);
+	drm_vblank_cleanup(dev);
+
+	pm_runtime_get_sync(dev->dev);
+	drm_irq_uninstall(dev);
+	pm_runtime_put_sync(dev->dev);
+
+#ifdef CONFIG_CPU_FREQ
+	cpufreq_unregister_notifier(&priv->freq_transition,
+			CPUFREQ_TRANSITION_NOTIFIER);
+#endif
+
+	if (priv->clk)
+		clk_put(priv->clk);
+
+	if (priv->mmio)
+		iounmap(priv->mmio);
+
+	dev->dev_private = NULL;
+
+	pm_runtime_disable(dev->dev);
+
+	list_for_each_entry(mod, &module_list, list) {
+		DBG("destroying module: %s", mod->name);
+		mod->funcs->destroy(mod);
+	}
+
+	kfree(priv);
+
+	return 0;
+}
+
+static int lcdc_load(struct drm_device *dev, unsigned long flags)
+{
+	struct platform_device *pdev = dev->platformdev;
+	struct lcdc_drm_private *priv;
+	struct resource *res;
+	int ret;
+
+	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+	if (!priv) {
+		dev_err(dev->dev, "failed to allocate private data\n");
+		return -ENOMEM;
+	}
+
+	dev->dev_private = priv;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev->dev, "failed to get memory resource\n");
+		ret = -EINVAL;
+		goto fail;
+	}
+
+	priv->mmio = ioremap_nocache(res->start, resource_size(res));
+	if (!priv->mmio) {
+		dev_err(dev->dev, "failed to ioremap\n");
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	priv->clk = clk_get(dev->dev, "fck");
+	if (IS_ERR(priv->clk)) {
+		dev_err(dev->dev, "failed to get functional clock\n");
+		ret = -ENODEV;
+		goto fail;
+	}
+
+	priv->disp_clk = clk_get(dev->dev, "dpll_disp_ck");
+	if (IS_ERR(priv->clk)) {
+		dev_err(dev->dev, "failed to get display clock\n");
+		ret = -ENODEV;
+		goto fail;
+	}
+
+#ifdef CONFIG_CPU_FREQ
+	priv->lcd_fck_rate = clk_get_rate(priv->clk);
+	priv->freq_transition.notifier_call = cpufreq_transition;
+	ret = cpufreq_register_notifier(&priv->freq_transition,
+			CPUFREQ_TRANSITION_NOTIFIER);
+	if (ret) {
+		dev_err(dev->dev, "failed to register cpufreq notifier\n");
+		goto fail;
+	}
+#endif
+
+	spin_lock_init(&priv->irq_lock);
+
+	pm_runtime_enable(dev->dev);
+
+	/* Determine LCD IP Version */
+	pm_runtime_get_sync(dev->dev);
+	switch (lcdc_read(dev, LCDC_PID_REG)) {
+	case 0x4C100102:
+		priv->rev = 1;
+		break;
+	case 0x4F200800:
+	case 0x4F201000:
+		priv->rev = 2;
+		break;
+	default:
+		dev_warn(dev->dev, "Unknown PID Reg value 0x%08x, "
+				"defaulting to LCD revision 1\n",
+				lcdc_read(dev, LCDC_PID_REG));
+		priv->rev = 1;
+		break;
+	}
+
+	pm_runtime_put_sync(dev->dev);
+
+	ret = modeset_init(dev);
+	if (ret < 0) {
+		dev_err(dev->dev, "failed to initialize mode setting\n");
+		goto fail;
+	}
+
+	ret = drm_vblank_init(dev, 1);
+	if (ret < 0) {
+		dev_err(dev->dev, "failed to initialize vblank\n");
+		goto fail;
+	}
+
+	pm_runtime_get_sync(dev->dev);
+	ret = drm_irq_install(dev);
+	pm_runtime_put_sync(dev->dev);
+	if (ret < 0) {
+		dev_err(dev->dev, "failed to install IRQ handler\n");
+		goto fail;
+	}
+
+	platform_set_drvdata(pdev, dev);
+
+	priv->fbdev = drm_fbdev_cma_init(dev, 16,
+			dev->mode_config.num_crtc,
+			dev->mode_config.num_connector);
+
+	drm_kms_helper_poll_init(dev);
+
+	return 0;
+
+fail:
+	lcdc_unload(dev);
+	return ret;
+}
+
+static void lcdc_preclose(struct drm_device *dev, struct drm_file *file)
+{
+	struct lcdc_drm_private *priv = dev->dev_private;
+
+	lcdc_crtc_cancel_page_flip(priv->crtc, file);
+}
+
+static void lcdc_lastclose(struct drm_device *dev)
+{
+	struct lcdc_drm_private *priv = dev->dev_private;
+	drm_fbdev_cma_restore_mode(priv->fbdev);
+}
+
+static irqreturn_t lcdc_irq(DRM_IRQ_ARGS)
+{
+	struct drm_device *dev = arg;
+	struct lcdc_drm_private *priv = dev->dev_private;
+	return lcdc_crtc_irq(priv->crtc);
+}
+
+static void lcdc_irq_preinstall(struct drm_device *dev)
+{
+	lcdc_clear_irqstatus(dev, 0xffffffff);
+}
+
+static int lcdc_irq_postinstall(struct drm_device *dev)
+{
+	struct lcdc_drm_private *priv = dev->dev_private;
+
+	/* enable FIFO underflow irq: */
+	if (priv->rev == 1) {
+		lcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_V1_UNDERFLOW_INT_ENA);
+	} else {
+		lcdc_set(dev, LCDC_INT_ENABLE_SET_REG, LCDC_V2_UNDERFLOW_INT_ENA);
+	}
+
+	return 0;
+}
+
+static void lcdc_irq_uninstall(struct drm_device *dev)
+{
+	struct lcdc_drm_private *priv = dev->dev_private;
+
+	/* disable irqs that we might have enabled: */
+	if (priv->rev == 1) {
+		lcdc_clear(dev, LCDC_RASTER_CTRL_REG,
+				LCDC_V1_UNDERFLOW_INT_ENA | LCDC_V1_PL_INT_ENA);
+		lcdc_clear(dev, LCDC_DMA_CTRL_REG, LCDC_V1_END_OF_FRAME_INT_ENA);
+	} else {
+		lcdc_clear(dev, LCDC_INT_ENABLE_SET_REG,
+			LCDC_V2_UNDERFLOW_INT_ENA | LCDC_V2_PL_INT_ENA |
+			LCDC_V2_END_OF_FRAME0_INT_ENA | LCDC_V2_END_OF_FRAME1_INT_ENA |
+			LCDC_FRAME_DONE);
+	}
+
+}
+
+static void enable_vblank(struct drm_device *dev, bool enable)
+{
+	struct lcdc_drm_private *priv = dev->dev_private;
+	unsigned long flags;
+	u32 reg, mask;
+
+	if (priv->rev == 1) {
+		reg = LCDC_DMA_CTRL_REG;
+		mask = LCDC_V1_END_OF_FRAME_INT_ENA;
+	} else {
+		reg = LCDC_INT_ENABLE_SET_REG;
+		mask = LCDC_V2_END_OF_FRAME0_INT_ENA |
+			LCDC_V2_END_OF_FRAME1_INT_ENA | LCDC_FRAME_DONE;
+	}
+
+	spin_lock_irqsave(&priv->irq_lock, flags);
+
+	if (enable)
+		lcdc_set(dev, reg, mask);
+	else
+		lcdc_clear(dev, reg, mask);
+
+	spin_unlock_irqrestore(&priv->irq_lock, flags);
+}
+
+static int lcdc_enable_vblank(struct drm_device *dev, int crtc)
+{
+	enable_vblank(dev, true);
+	return 0;
+}
+
+static void lcdc_disable_vblank(struct drm_device *dev, int crtc)
+{
+	enable_vblank(dev, false);
+}
+
+#if defined(CONFIG_DEBUG_FS) || defined(CONFIG_PM_SLEEP)
+static const struct {
+	const char *name;
+	uint8_t  rev;
+	uint8_t  save;
+	uint32_t reg;
+} registers[] = 	{
+#define REG(rev, save, reg) { #reg, rev, save, reg }
+		/* exists in revision 1: */
+		REG(1, false, LCDC_PID_REG),
+		REG(1, true,  LCDC_CTRL_REG),
+		REG(1, false, LCDC_STAT_REG),
+		REG(1, true,  LCDC_RASTER_CTRL_REG),
+		REG(1, true,  LCDC_RASTER_TIMING_0_REG),
+		REG(1, true,  LCDC_RASTER_TIMING_1_REG),
+		REG(1, true,  LCDC_RASTER_TIMING_2_REG),
+		REG(1, true,  LCDC_DMA_CTRL_REG),
+		REG(1, true,  LCDC_DMA_FRM_BUF_BASE_ADDR_0_REG),
+		REG(1, true,  LCDC_DMA_FRM_BUF_CEILING_ADDR_0_REG),
+		REG(1, true,  LCDC_DMA_FRM_BUF_BASE_ADDR_1_REG),
+		REG(1, true,  LCDC_DMA_FRM_BUF_CEILING_ADDR_1_REG),
+		/* new in revision 2: */
+		REG(2, false, LCDC_RAW_STAT_REG),
+		REG(2, false, LCDC_MASKED_STAT_REG),
+		REG(2, false, LCDC_INT_ENABLE_SET_REG),
+		REG(2, false, LCDC_INT_ENABLE_CLR_REG),
+		REG(2, false, LCDC_END_OF_INT_IND_REG),
+		REG(2, true,  LCDC_CLK_ENABLE_REG),
+		REG(2, true,  LCDC_INT_ENABLE_SET_REG),
+#undef REG
+};
+#endif
+
+#ifdef CONFIG_DEBUG_FS
+static int lcdc_regs_show(struct seq_file *m, void *arg)
+{
+	struct drm_info_node *node = (struct drm_info_node *) m->private;
+	struct drm_device *dev = node->minor->dev;
+	struct lcdc_drm_private *priv = dev->dev_private;
+	unsigned i;
+
+	pm_runtime_get_sync(dev->dev);
+
+	seq_printf(m, "revision: %d\n", priv->rev);
+
+	for (i = 0; i < ARRAY_SIZE(registers); i++)
+		if (priv->rev >= registers[i].rev)
+			seq_printf(m, "%s:\t %08x\n", registers[i].name,
+					lcdc_read(dev, registers[i].reg));
+
+	pm_runtime_put_sync(dev->dev);
+
+	return 0;
+}
+
+static int lcdc_mm_show(struct seq_file *m, void *arg)
+{
+	struct drm_info_node *node = (struct drm_info_node *) m->private;
+	struct drm_device *dev = node->minor->dev;
+	return drm_mm_dump_table(m, dev->mm_private);
+}
+
+static struct drm_info_list lcdc_debugfs_list[] = {
+		{ "regs", lcdc_regs_show, 0 },
+		{ "mm",   lcdc_mm_show,   0 },
+		{ "fb",   drm_fb_cma_debugfs_show, 0 },
+};
+
+static int lcdc_debugfs_init(struct drm_minor *minor)
+{
+	struct drm_device *dev = minor->dev;
+	struct lcdc_module *mod;
+	int ret;
+
+	ret = drm_debugfs_create_files(lcdc_debugfs_list,
+			ARRAY_SIZE(lcdc_debugfs_list),
+			minor->debugfs_root, minor);
+
+	list_for_each_entry(mod, &module_list, list)
+		if (mod->funcs->debugfs_init)
+			mod->funcs->debugfs_init(mod, minor);
+
+	if (ret) {
+		dev_err(dev->dev, "could not install lcdc_debugfs_list\n");
+		return ret;
+	}
+
+	return ret;
+}
+
+static void lcdc_debugfs_cleanup(struct drm_minor *minor)
+{
+	struct lcdc_module *mod;
+	drm_debugfs_remove_files(lcdc_debugfs_list,
+			ARRAY_SIZE(lcdc_debugfs_list), minor);
+
+	list_for_each_entry(mod, &module_list, list)
+		if (mod->funcs->debugfs_cleanup)
+			mod->funcs->debugfs_cleanup(mod, minor);
+}
+#endif
+
+static const struct file_operations fops = {
+	.owner              = THIS_MODULE,
+	.open               = drm_open,
+	.release            = drm_release,
+	.unlocked_ioctl     = drm_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl       = drm_compat_ioctl,
+#endif
+	.poll               = drm_poll,
+	.read               = drm_read,
+	.fasync             = drm_fasync,
+	.llseek             = no_llseek,
+	.mmap               = drm_gem_cma_mmap,
+};
+
+static struct drm_driver lcdc_driver = {
+	.driver_features    = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET,
+	.load               = lcdc_load,
+	.unload             = lcdc_unload,
+	.preclose           = lcdc_preclose,
+	.lastclose          = lcdc_lastclose,
+	.irq_handler        = lcdc_irq,
+	.irq_preinstall     = lcdc_irq_preinstall,
+	.irq_postinstall    = lcdc_irq_postinstall,
+	.irq_uninstall      = lcdc_irq_uninstall,
+	.get_vblank_counter = drm_vblank_count,
+	.enable_vblank      = lcdc_enable_vblank,
+	.disable_vblank     = lcdc_disable_vblank,
+	.gem_free_object    = drm_gem_cma_free_object,
+	.gem_vm_ops         = &drm_gem_cma_vm_ops,
+	.dumb_create        = drm_gem_cma_dumb_create,
+	.dumb_map_offset    = drm_gem_cma_dumb_map_offset,
+	.dumb_destroy       = drm_gem_cma_dumb_destroy,
+#ifdef CONFIG_DEBUG_FS
+	.debugfs_init       = lcdc_debugfs_init,
+	.debugfs_cleanup    = lcdc_debugfs_cleanup,
+#endif
+	.fops               = &fops,
+	.name               = "lcdc",
+	.desc               = "TI LCD Controller DRM",
+	.date               = "20121205",
+	.major              = 1,
+	.minor              = 0,
+};
+
+/*
+ * Power management:
+ */
+
+#if CONFIG_PM_SLEEP
+static int lcdc_pm_suspend(struct device *dev)
+{
+	struct drm_device *ddev = dev_get_drvdata(dev);
+	struct lcdc_drm_private *priv = ddev->dev_private;
+	unsigned i, n = 0;
+
+	drm_kms_helper_poll_disable(ddev);
+
+	/* Save register state: */
+	for (i = 0; i < ARRAY_SIZE(registers); i++)
+		if (registers[i].save && (priv->rev >= registers[i].rev))
+			priv->saved_register[n++] = lcdc_read(ddev, registers[i].reg);
+
+	return 0;
+}
+
+static int lcdc_pm_resume(struct device *dev)
+{
+	struct drm_device *ddev = dev_get_drvdata(dev);
+	struct lcdc_drm_private *priv = ddev->dev_private;
+	unsigned i, n = 0;
+
+	/* Restore register state: */
+	for (i = 0; i < ARRAY_SIZE(registers); i++)
+		if (registers[i].save && (priv->rev >= registers[i].rev))
+			lcdc_write(ddev, registers[i].reg, priv->saved_register[n++]);
+
+	drm_kms_helper_poll_enable(ddev);
+
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops lcdc_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(lcdc_pm_suspend, lcdc_pm_resume)
+};
+
+/*
+ * Platform driver:
+ */
+
+static int __devinit lcdc_pdev_probe(struct platform_device *pdev)
+{
+	/* bail out early if no DT data: */
+	if (!of_match_device(lcdc_of_match, &pdev->dev)) {
+		dev_err(&pdev->dev, "device-tree data is missing\n");
+		return -ENXIO;
+	}
+
+	return drm_platform_init(&lcdc_driver, pdev);
+}
+
+static int __devexit lcdc_pdev_remove(struct platform_device *pdev)
+{
+	drm_platform_exit(&lcdc_driver, pdev);
+
+	return 0;
+}
+
+static struct of_device_id lcdc_of_match[] = {
+		{ .compatible = "ti,am33xx-lcdc", },
+		{ },
+};
+MODULE_DEVICE_TABLE(of, lcdc_of_match);
+
+static struct platform_driver lcdc_platform_driver = {
+	.probe      = lcdc_pdev_probe,
+	.remove     = __devexit_p(lcdc_pdev_remove),
+	.driver     = {
+		.owner  = THIS_MODULE,
+		.name   = "lcdc",
+		.pm     = &lcdc_pm_ops,
+		.of_match_table = lcdc_of_match,
+	},
+};
+
+static int __init lcdc_drm_init(void)
+{
+	DBG("init");
+	lcdc_tfp410_init();
+	return platform_driver_register(&lcdc_platform_driver);
+}
+
+static void __exit lcdc_drm_fini(void)
+{
+	DBG("fini");
+	lcdc_tfp410_fini();
+	platform_driver_unregister(&lcdc_platform_driver);
+}
+
+module_init(lcdc_drm_init);
+module_exit(lcdc_drm_fini);
+
+MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
+MODULE_DESCRIPTION("TI LCD Controller DRM Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/lcdc/lcdc_drv.h b/drivers/gpu/drm/lcdc/lcdc_drv.h
new file mode 100644
index 0000000..14b93a2c
--- /dev/null
+++ b/drivers/gpu/drm/lcdc/lcdc_drv.h
@@ -0,0 +1,162 @@ 
+/*
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LCDC_DRV_H__
+#define __LCDC_DRV_H__
+
+#include <linux/clk.h>
+#include <linux/cpufreq.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/list.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+
+struct lcdc_drm_private {
+	void __iomem *mmio;
+
+	struct clk *disp_clk;    /* display dpll */
+	struct clk *clk;         /* functional clock */
+	int rev;                 /* IP revision */
+
+	/* register contents saved across suspend/resume: */
+	u32 saved_register[12];
+
+	spinlock_t irq_lock; // TODO do we need this?
+
+#ifdef CONFIG_CPU_FREQ
+	struct notifier_block freq_transition;
+	unsigned int lcd_fck_rate;
+#endif
+
+	struct drm_fbdev_cma *fbdev;
+
+	struct drm_crtc *crtc;
+
+	unsigned int num_encoders;
+	struct drm_encoder *encoders[8];
+
+	unsigned int num_connectors;
+	struct drm_connector *connectors[8];
+};
+
+/* Sub-module for display.  Since we don't know at compile time what panels
+ * or display adapter(s) might be present (for ex, off chip dvi/tfp410,
+ * hdmi encoder, various lcd panels), the connector/encoder(s) are split into
+ * separate drivers.  If they are probed and found to be present, they
+ * register themselves with lcdc_register_module().
+ */
+struct lcdc_module;
+
+struct lcdc_module_ops {
+	/* create appropriate encoders/connectors: */
+	int (*modeset_init)(struct lcdc_module *mod, struct drm_device *dev);
+	void (*destroy)(struct lcdc_module *mod);
+#ifdef CONFIG_DEBUG_FS
+	/* create debugfs nodes (can be NULL): */
+	int (*debugfs_init)(struct lcdc_module *mod, struct drm_minor *minor);
+	/* cleanup debugfs nodes (can be NULL): */
+	void (*debugfs_cleanup)(struct lcdc_module *mod, struct drm_minor *minor);
+#endif
+};
+
+struct lcdc_module {
+	const char *name;
+	struct list_head list;
+	const struct lcdc_module_ops *funcs;
+};
+
+void lcdc_module_init(struct lcdc_module *mod, const char *name,
+		const struct lcdc_module_ops *funcs);
+void lcdc_module_cleanup(struct lcdc_module *mod);
+
+
+/* Panel config that needs to be set in the crtc, but is not coming from
+ * the mode timings.  The display module is expected to call
+ * lcdc_crtc_set_panel_info() to set this during modeset.
+ */
+struct lcdc_panel_info {
+
+	int max_bpp;
+	int min_bpp;
+
+	/* AC Bias Pin Frequency */
+	int ac_bias;
+
+	/* AC Bias Pin Transitions per Interrupt */
+	int ac_bias_intrpt;
+
+	/* DMA burst size */
+	int dma_burst_sz;
+
+	/* Bits per pixel */
+	int bpp;
+
+	/* FIFO DMA Request Delay */
+	int fdd;
+
+	/* TFT Alternative Signal Mapping (Only for active) */
+	unsigned char tft_alt_mode;
+
+	/* 12 Bit Per Pixel (5-6-5) Mode (Only for passive) */
+	unsigned char stn_565_mode;
+
+	/* Mono 8-bit Mode: 1=D0-D7 or 0=D0-D3 */
+	unsigned char mono_8bit_mode;
+
+	/* Invert line clock */
+	unsigned char invert_line_clock;
+
+	/* Invert frame clock  */
+	unsigned char invert_frm_clock;
+
+	/* Invert pixel clock */
+	unsigned char invert_pxl_clk;
+
+	/* Horizontal and Vertical Sync Edge: 0=rising 1=falling */
+	unsigned char sync_edge;
+
+	/* Horizontal and Vertical Sync: Control: 0=ignore */
+	unsigned char sync_ctrl;
+
+	/* Raster Data Order Select: 1=Most-to-least 0=Least-to-most */
+	unsigned char raster_order;
+
+	/* DMA FIFO threshold */
+	int fifo_th;
+};
+
+#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
+
+struct drm_crtc *lcdc_crtc_create(struct drm_device *dev);
+void lcdc_crtc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file);
+irqreturn_t lcdc_crtc_irq(struct drm_crtc *crtc);
+void lcdc_crtc_update_clk(struct drm_crtc *crtc);
+void lcdc_crtc_set_panel_info(struct drm_crtc *crtc,
+		const struct lcdc_panel_info *info);
+int lcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode);
+int lcdc_crtc_max_width(struct drm_crtc *crtc);
+
+#endif /* __LCDC_DRV_H__ */
diff --git a/drivers/gpu/drm/lcdc/lcdc_regs.h b/drivers/gpu/drm/lcdc/lcdc_regs.h
new file mode 100644
index 0000000..1ae0442
--- /dev/null
+++ b/drivers/gpu/drm/lcdc/lcdc_regs.h
@@ -0,0 +1,154 @@ 
+/*
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LCDC_REGS_H__
+#define __LCDC_REGS_H__
+
+/* LCDC register definitions, based on da8xx-fb */
+
+#include <linux/bitops.h>
+
+#include "lcdc_drv.h"
+
+/* LCDC Status Register */
+#define LCDC_END_OF_FRAME1                       BIT(9)
+#define LCDC_END_OF_FRAME0                       BIT(8)
+#define LCDC_PL_LOAD_DONE                        BIT(6)
+#define LCDC_FIFO_UNDERFLOW                      BIT(5)
+#define LCDC_SYNC_LOST                           BIT(2)
+#define LCDC_FRAME_DONE                          BIT(0)
+
+/* LCDC DMA Control Register */
+#define LCDC_DMA_BURST_SIZE(x)                   ((x) << 4)
+#define LCDC_DMA_BURST_1                         0x0
+#define LCDC_DMA_BURST_2                         0x1
+#define LCDC_DMA_BURST_4                         0x2
+#define LCDC_DMA_BURST_8                         0x3
+#define LCDC_DMA_BURST_16                        0x4
+#define LCDC_V1_END_OF_FRAME_INT_ENA             BIT(2)
+#define LCDC_V2_END_OF_FRAME0_INT_ENA            BIT(8)
+#define LCDC_V2_END_OF_FRAME1_INT_ENA            BIT(9)
+#define LCDC_DUAL_FRAME_BUFFER_ENABLE            BIT(0)
+
+/* LCDC Control Register */
+#define LCDC_CLK_DIVISOR(x)                      ((x) << 8)
+#define LCDC_RASTER_MODE                         0x01
+
+/* LCDC Raster Control Register */
+#define LCDC_PALETTE_LOAD_MODE(x)                ((x) << 20)
+#define PALETTE_AND_DATA                         0x00
+#define PALETTE_ONLY                             0x01
+#define DATA_ONLY                                0x02
+
+#define LCDC_MONO_8BIT_MODE                      BIT(9)
+#define LCDC_RASTER_ORDER                        BIT(8)
+#define LCDC_TFT_MODE                            BIT(7)
+#define LCDC_V1_UNDERFLOW_INT_ENA                BIT(6)
+#define LCDC_V2_UNDERFLOW_INT_ENA                BIT(5)
+#define LCDC_V1_PL_INT_ENA                       BIT(4)
+#define LCDC_V2_PL_INT_ENA                       BIT(6)
+#define LCDC_MONOCHROME_MODE                     BIT(1)
+#define LCDC_RASTER_ENABLE                       BIT(0)
+#define LCDC_TFT_ALT_ENABLE                      BIT(23)
+#define LCDC_STN_565_ENABLE                      BIT(24)
+#define LCDC_V2_DMA_CLK_EN                       BIT(2)
+#define LCDC_V2_LIDD_CLK_EN                      BIT(1)
+#define LCDC_V2_CORE_CLK_EN                      BIT(0)
+#define LCDC_V2_LPP_B10                          26
+#define LCDC_V2_TFT_24BPP_MODE                   BIT(25)
+#define LCDC_V2_TFT_24BPP_UNPACK                 BIT(26)
+
+/* LCDC Raster Timing 2 Register */
+#define LCDC_AC_BIAS_TRANSITIONS_PER_INT(x)      ((x) << 16)
+#define LCDC_AC_BIAS_FREQUENCY(x)                ((x) << 8)
+#define LCDC_SYNC_CTRL                           BIT(25)
+#define LCDC_SYNC_EDGE                           BIT(24)
+#define LCDC_INVERT_PIXEL_CLOCK                  BIT(22)
+#define LCDC_INVERT_LINE_CLOCK                   BIT(21)
+#define LCDC_INVERT_FRAME_CLOCK                  BIT(20)
+
+/* LCDC Block */
+#define LCDC_PID_REG                             0x0
+#define LCDC_CTRL_REG                            0x4
+#define LCDC_STAT_REG                            0x8
+#define LCDC_RASTER_CTRL_REG                     0x28
+#define LCDC_RASTER_TIMING_0_REG                 0x2c
+#define LCDC_RASTER_TIMING_1_REG                 0x30
+#define LCDC_RASTER_TIMING_2_REG                 0x34
+#define LCDC_DMA_CTRL_REG                        0x40
+#define LCDC_DMA_FRM_BUF_BASE_ADDR_0_REG         0x44
+#define LCDC_DMA_FRM_BUF_CEILING_ADDR_0_REG      0x48
+#define LCDC_DMA_FRM_BUF_BASE_ADDR_1_REG         0x4c
+#define LCDC_DMA_FRM_BUF_CEILING_ADDR_1_REG      0x50
+
+/* Interrupt Registers available only in Version 2 */
+#define LCDC_RAW_STAT_REG                        0x58
+#define LCDC_MASKED_STAT_REG                     0x5c
+#define LCDC_INT_ENABLE_SET_REG                  0x60
+#define LCDC_INT_ENABLE_CLR_REG                  0x64
+#define LCDC_END_OF_INT_IND_REG                  0x68
+
+/* Clock registers available only on Version 2 */
+#define LCDC_CLK_ENABLE_REG                      0x6c
+#define LCDC_CLK_RESET_REG                       0x70
+#define LCDC_CLK_MAIN_RESET                      BIT(3)
+
+
+/*
+ * Helpers:
+ */
+
+static inline void lcdc_write(struct drm_device *dev, u32 reg, u32 data)
+{
+	struct lcdc_drm_private *priv = dev->dev_private;
+	iowrite32(data, priv->mmio + reg);
+}
+
+static inline u32 lcdc_read(struct drm_device *dev, u32 reg)
+{
+	struct lcdc_drm_private *priv = dev->dev_private;
+	return ioread32(priv->mmio + reg);
+}
+
+static inline void lcdc_set(struct drm_device *dev, u32 reg, u32 mask)
+{
+	lcdc_write(dev, reg, lcdc_read(dev, reg) | mask);
+}
+
+static inline void lcdc_clear(struct drm_device *dev, u32 reg, u32 mask)
+{
+	lcdc_write(dev, reg, lcdc_read(dev, reg) & ~mask);
+}
+
+/* the register to read/clear irqstatus differs between v1 and v2 of the IP */
+static inline u32 lcdc_irqstatus_reg(struct drm_device *dev)
+{
+	struct lcdc_drm_private *priv = dev->dev_private;
+	return (priv->rev == 2) ? LCDC_MASKED_STAT_REG : LCDC_STAT_REG;
+}
+
+static inline u32 lcdc_read_irqstatus(struct drm_device *dev)
+{
+	return lcdc_read(dev, lcdc_irqstatus_reg(dev));
+}
+
+static inline void lcdc_clear_irqstatus(struct drm_device *dev, u32 mask)
+{
+	lcdc_write(dev, lcdc_irqstatus_reg(dev), mask);
+}
+
+#endif /* __LCDC_REGS_H__ */
diff --git a/drivers/gpu/drm/lcdc/lcdc_tfp410.c b/drivers/gpu/drm/lcdc/lcdc_tfp410.c
new file mode 100644
index 0000000..84f90a8
--- /dev/null
+++ b/drivers/gpu/drm/lcdc/lcdc_tfp410.c
@@ -0,0 +1,424 @@ 
+/*
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/i2c.h>
+#include <linux/of_i2c.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/pinctrl/pinmux.h>
+#include <linux/pinctrl/consumer.h>
+
+#include "lcdc_drv.h"
+
+struct tfp410_module {
+	struct lcdc_module base;
+	struct i2c_adapter *i2c;
+	int gpio;
+};
+#define to_tfp410_module(x) container_of(x, struct tfp410_module, base)
+
+
+static const struct lcdc_panel_info dvi_info = {
+        .min_bpp                = 16,
+        .max_bpp                = 16,
+        .ac_bias                = 255,
+        .ac_bias_intrpt         = 0,
+        .dma_burst_sz           = 16,
+        .bpp                    = 16,
+        .fdd                    = 0x80,
+        .tft_alt_mode           = 0,
+        .stn_565_mode           = 0,
+        .mono_8bit_mode         = 0,
+        .invert_line_clock      = 1,
+        .invert_frm_clock       = 1,
+        .sync_edge              = 0,
+        .sync_ctrl              = 1,
+        .raster_order           = 0,
+};
+
+/*
+ * Encoder:
+ */
+
+struct tfp410_encoder {
+	struct drm_encoder base;
+	struct tfp410_module *mod;
+	int dpms;
+};
+#define to_tfp410_encoder(x) container_of(x, struct tfp410_encoder, base)
+
+
+static void tfp410_encoder_destroy(struct drm_encoder *encoder)
+{
+	struct tfp410_encoder *tfp410_encoder = to_tfp410_encoder(encoder);
+	drm_encoder_cleanup(encoder);
+	kfree(tfp410_encoder);
+}
+
+static void tfp410_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+	struct tfp410_encoder *tfp410_encoder = to_tfp410_encoder(encoder);
+
+	if (tfp410_encoder->dpms == mode)
+		return;
+
+	if (mode == DRM_MODE_DPMS_ON) {
+		DBG("Power on");
+		gpio_direction_output(tfp410_encoder->mod->gpio, 1);
+	} else {
+		DBG("Power off");
+		gpio_direction_output(tfp410_encoder->mod->gpio, 0);
+	}
+
+	tfp410_encoder->dpms = mode;
+}
+
+static bool tfp410_encoder_mode_fixup(struct drm_encoder *encoder,
+		const struct drm_display_mode *mode,
+		struct drm_display_mode *adjusted_mode)
+{
+	/* nothing needed */
+	return true;
+}
+
+static void tfp410_encoder_prepare(struct drm_encoder *encoder)
+{
+	tfp410_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
+	lcdc_crtc_set_panel_info(encoder->crtc, &dvi_info);
+}
+
+static void tfp410_encoder_commit(struct drm_encoder *encoder)
+{
+	tfp410_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
+}
+
+static void tfp410_encoder_mode_set(struct drm_encoder *encoder,
+		struct drm_display_mode *mode,
+		struct drm_display_mode *adjusted_mode)
+{
+	/* nothing needed */
+}
+
+static const struct drm_encoder_funcs tfp410_encoder_funcs = {
+		.destroy        = tfp410_encoder_destroy,
+};
+
+static const struct drm_encoder_helper_funcs tfp410_encoder_helper_funcs = {
+		.dpms           = tfp410_encoder_dpms,
+		.mode_fixup     = tfp410_encoder_mode_fixup,
+		.prepare        = tfp410_encoder_prepare,
+		.commit         = tfp410_encoder_commit,
+		.mode_set       = tfp410_encoder_mode_set,
+};
+
+static struct drm_encoder *tfp410_encoder_create(struct drm_device *dev,
+		struct tfp410_module *mod)
+{
+	struct tfp410_encoder *tfp410_encoder;
+	struct drm_encoder *encoder;
+	int ret;
+
+	tfp410_encoder = kzalloc(sizeof(*tfp410_encoder), GFP_KERNEL);
+	if (!tfp410_encoder) {
+		dev_err(dev->dev, "allocation failed\n");
+		return NULL;
+	}
+
+	tfp410_encoder->dpms = DRM_MODE_DPMS_OFF;
+	tfp410_encoder->mod = mod;
+
+	encoder = &tfp410_encoder->base;
+	encoder->possible_crtcs = 1;
+
+	ret = drm_encoder_init(dev, encoder, &tfp410_encoder_funcs,
+			DRM_MODE_ENCODER_TMDS);
+	if (ret < 0)
+		goto fail;
+
+	drm_encoder_helper_add(encoder, &tfp410_encoder_helper_funcs);
+
+	return encoder;
+
+fail:
+	tfp410_encoder_destroy(encoder);
+	return NULL;
+}
+
+/*
+ * Connector:
+ */
+
+struct tfp410_connector {
+	struct drm_connector base;
+
+	struct drm_encoder *encoder;  /* our connected encoder */
+	struct tfp410_module *mod;
+};
+#define to_tfp410_connector(x) container_of(x, struct tfp410_connector, base)
+
+
+static void tfp410_connector_destroy(struct drm_connector *connector)
+{
+	struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector);
+	drm_connector_cleanup(connector);
+	kfree(tfp410_connector);
+}
+
+static enum drm_connector_status tfp410_connector_detect(
+		struct drm_connector *connector,
+		bool force)
+{
+	struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector);
+
+	if (drm_probe_ddc(tfp410_connector->mod->i2c))
+		return connector_status_connected;
+
+	return connector_status_unknown;
+}
+
+static int tfp410_connector_get_modes(struct drm_connector *connector)
+{
+	struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector);
+	struct edid *edid;
+	int ret = 0;
+
+	edid = drm_get_edid(connector, tfp410_connector->mod->i2c);
+
+	drm_mode_connector_update_edid_property(connector, edid);
+
+	if (edid) {
+		ret = drm_add_edid_modes(connector, edid);
+		kfree(edid);
+	}
+
+	return ret;
+}
+
+static int tfp410_connector_mode_valid(struct drm_connector *connector,
+		  struct drm_display_mode *mode)
+{
+	struct lcdc_drm_private *priv = connector->dev->dev_private;
+	/* our only constraints are what the crtc can generate: */
+	return lcdc_crtc_mode_valid(priv->crtc, mode);
+}
+
+static struct drm_encoder *tfp410_connector_best_encoder(
+		struct drm_connector *connector)
+{
+	struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector);
+	return tfp410_connector->encoder;
+}
+
+static const struct drm_connector_funcs tfp410_connector_funcs = {
+	.destroy            = tfp410_connector_destroy,
+	.dpms               = drm_helper_connector_dpms,
+	.detect             = tfp410_connector_detect,
+	.fill_modes         = drm_helper_probe_single_connector_modes,
+};
+
+static const struct drm_connector_helper_funcs tfp410_connector_helper_funcs = {
+	.get_modes          = tfp410_connector_get_modes,
+	.mode_valid         = tfp410_connector_mode_valid,
+	.best_encoder       = tfp410_connector_best_encoder,
+};
+
+static struct drm_connector *tfp410_connector_create(struct drm_device *dev,
+		struct tfp410_module *mod, struct drm_encoder *encoder)
+{
+	struct tfp410_connector *tfp410_connector;
+	struct drm_connector *connector;
+	int ret;
+
+	tfp410_connector = kzalloc(sizeof(*tfp410_connector), GFP_KERNEL);
+	if (!tfp410_connector) {
+		dev_err(dev->dev, "allocation failed\n");
+		return NULL;
+	}
+
+	tfp410_connector->encoder = encoder;
+	tfp410_connector->mod = mod;
+
+	connector = &tfp410_connector->base;
+
+	drm_connector_init(dev, connector, &tfp410_connector_funcs,
+			DRM_MODE_CONNECTOR_DVID);
+	drm_connector_helper_add(connector, &tfp410_connector_helper_funcs);
+
+	connector->polled = DRM_CONNECTOR_POLL_CONNECT |
+			DRM_CONNECTOR_POLL_DISCONNECT;
+
+	connector->interlace_allowed = 0;
+	connector->doublescan_allowed = 0;
+
+	ret = drm_mode_connector_attach_encoder(connector, encoder);
+	if (ret)
+		goto fail;
+
+	drm_sysfs_connector_add(connector);
+
+	return connector;
+
+fail:
+	tfp410_connector_destroy(connector);
+	return NULL;
+}
+
+/*
+ * Module:
+ */
+
+static int tfp410_modeset_init(struct lcdc_module *mod, struct drm_device *dev)
+{
+	struct tfp410_module *tfp410_mod = to_tfp410_module(mod);
+	struct lcdc_drm_private *priv = dev->dev_private;
+	struct drm_encoder *encoder;
+	struct drm_connector *connector;
+
+	encoder = tfp410_encoder_create(dev, tfp410_mod);
+	if (!encoder)
+		return -ENOMEM;
+
+	connector = tfp410_connector_create(dev, tfp410_mod, encoder);
+	if (!connector)
+		return -ENOMEM;
+
+	priv->encoders[priv->num_encoders++] = encoder;
+	priv->connectors[priv->num_connectors++] = connector;
+
+	return 0;
+}
+
+static void tfp410_destroy(struct lcdc_module *mod)
+{
+	struct tfp410_module *tfp410_mod = to_tfp410_module(mod);
+
+	if (tfp410_mod->i2c)
+		i2c_put_adapter(tfp410_mod->i2c);
+
+	if (!IS_ERR_VALUE(tfp410_mod->gpio))
+		gpio_free(tfp410_mod->gpio);
+
+	lcdc_module_cleanup(mod);
+}
+
+static const struct lcdc_module_ops tfp410_module_ops = {
+		.modeset_init = tfp410_modeset_init,
+		.destroy = tfp410_destroy,
+};
+
+/*
+ * Device:
+ */
+
+static struct of_device_id tfp410_of_match[];
+
+static int __devinit tfp410_probe(struct platform_device *pdev)
+{
+	struct device_node *node = pdev->dev.of_node;
+	struct device_node *i2c_node;
+	struct tfp410_module *tfp410_mod;
+	struct lcdc_module *mod;
+	struct pinctrl *pinctrl;
+	uint32_t i2c_phandle;
+	int ret = -EINVAL;
+
+	/* bail out early if no DT data: */
+	if (!of_match_device(tfp410_of_match, &pdev->dev)) {
+		dev_err(&pdev->dev, "device-tree data is missing\n");
+		return -ENXIO;
+	}
+
+	tfp410_mod = kzalloc(sizeof(*tfp410_mod), GFP_KERNEL);
+	if (!tfp410_mod)
+		return -ENOMEM;
+
+	mod = &tfp410_mod->base;
+
+	lcdc_module_init(mod, "tfp410", &tfp410_module_ops);
+
+	pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
+	if (IS_ERR(pinctrl))
+		dev_warn(&pdev->dev, "pins are not configured\n");
+
+	if (of_property_read_u32(node, "i2c", &i2c_phandle)) {
+		dev_err(&pdev->dev, "could not get i2c bus phandle\n");
+		goto fail;
+	}
+
+	i2c_node = of_find_node_by_phandle(i2c_phandle);
+	if (!i2c_node) {
+		dev_err(&pdev->dev, "could not get i2c bus node\n");
+		goto fail;
+	}
+
+	tfp410_mod->i2c = of_find_i2c_adapter_by_node(i2c_node);
+	if (!tfp410_mod->i2c) {
+		dev_err(&pdev->dev, "could not get i2c\n");
+		goto fail;
+	}
+
+	of_node_put(i2c_node);
+
+	tfp410_mod->gpio = of_get_named_gpio_flags(node, "powerdn-gpio",
+			0, NULL);
+	if (IS_ERR_VALUE(tfp410_mod->gpio)) {
+		dev_warn(&pdev->dev, "No power down GPIO\n");
+	} else {
+		ret = gpio_request(tfp410_mod->gpio, "DVI_PDn");
+		if (ret) {
+			dev_err(&pdev->dev, "could not get DVI_PDn gpio\n");
+			goto fail;
+		}
+	}
+
+	return 0;
+
+fail:
+	tfp410_destroy(mod);
+	return ret;
+}
+
+static int __devexit tfp410_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static struct of_device_id tfp410_of_match[] = {
+		{ .compatible = "tfp410", },
+		{ },
+};
+MODULE_DEVICE_TABLE(of, tfp410_of_match);
+
+struct platform_driver tfp410_driver = {
+	.probe = tfp410_probe,
+	.remove = tfp410_remove,
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = "tfp410",
+		.of_match_table = tfp410_of_match,
+	},
+};
+
+int __init lcdc_tfp410_init(void)
+{
+	return platform_driver_register(&tfp410_driver);
+}
+
+void __exit lcdc_tfp410_fini(void)
+{
+	platform_driver_unregister(&tfp410_driver);
+}
diff --git a/drivers/gpu/drm/lcdc/lcdc_tfp410.h b/drivers/gpu/drm/lcdc/lcdc_tfp410.h
new file mode 100644
index 0000000..4221e69
--- /dev/null
+++ b/drivers/gpu/drm/lcdc/lcdc_tfp410.h
@@ -0,0 +1,26 @@ 
+/*
+ * Copyright (C) 2012 Texas Instruments
+ * Author: Rob Clark <robdclark@gmail.com>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __LCDC_TFP410_H__
+#define __LCDC_TFP410_H__
+
+/* sub-module for tfp410 dvi adaptor */
+
+int lcdc_tfp410_init(void);
+void lcdc_tfp410_fini(void);
+
+#endif /* __LCDC_TFP410_H__ */