All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/4] TI LCDC DRM driver
@ 2013-01-22 22:36 ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-22 22:36 UTC (permalink / raw)
  To: dri-devel; +Cc: patches, linux-omap, linux-arm-kernel, Rob Clark

I think this driver is ready to go, so I've removed the 'RFC' tag.  Prove
me wrong by reviewing the patches and sending comments ;-)

The first patch adds the basic driver and TFP410 DVI output.

The second patch adds support for NXP TDA998x family of i2c connected
HDMI encoders.  It is split out into an i2c encoder-slave in case someone
else has hw with the same HDMI encoder.

The final patch adds support for LCD panels, with timings and panel info
coming from device-tree.

The patch set has dependencies on the following patches that I have sent
earlier (which have not changed since then so I am not resending):

 * drm/cma: add debugfs helpers
 * drm: i2c encoder helper wrappers

In addition, the LCD panel patch also depends on Steffen Trumtrar's OF
display helper series. I've moved this patch to last so it can be merged
later if needed.  Although I really see no reason not to merge the OF
display helper series for 3.9.

These patches and their dependencies can also be found here:

  git://people.freedesktop.org/~robclark/linux tilcdc-next
  http://cgit.freedesktop.org/~robclark/linux/log/?h=tilcdc-next

Rob Clark (4):
  drm/tilcdc: add TI LCD Controller DRM driver (v3)
  drm/i2c: nxp-tda998x (v2)
  drm/tilcdc: add encoder slave
  drm/tilcdc: add support for LCD panels (v4)

 drivers/gpu/drm/Kconfig                |   2 +
 drivers/gpu/drm/Makefile               |   1 +
 drivers/gpu/drm/i2c/Makefile           |   3 +
 drivers/gpu/drm/i2c/tda998x_drv.c      | 908 +++++++++++++++++++++++++++++++++
 drivers/gpu/drm/tilcdc/Kconfig         |  25 +
 drivers/gpu/drm/tilcdc/Makefile        |  10 +
 drivers/gpu/drm/tilcdc/tilcdc_crtc.c   | 597 ++++++++++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_drv.c    | 611 ++++++++++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_drv.h    | 159 ++++++
 drivers/gpu/drm/tilcdc/tilcdc_panel.c  | 443 ++++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_panel.h  |  26 +
 drivers/gpu/drm/tilcdc/tilcdc_regs.h   | 154 ++++++
 drivers/gpu/drm/tilcdc/tilcdc_slave.c  | 380 ++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_slave.h  |  26 +
 drivers/gpu/drm/tilcdc/tilcdc_tfp410.c | 423 +++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_tfp410.h |  26 +
 16 files changed, 3794 insertions(+)
 create mode 100644 drivers/gpu/drm/i2c/tda998x_drv.c
 create mode 100644 drivers/gpu/drm/tilcdc/Kconfig
 create mode 100644 drivers/gpu/drm/tilcdc/Makefile
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_crtc.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.h
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_panel.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_panel.h
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_regs.h
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.h
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.h

-- 
1.8.1


^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 0/4] TI LCDC DRM driver
@ 2013-01-22 22:36 ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-22 22:36 UTC (permalink / raw)
  To: linux-arm-kernel

I think this driver is ready to go, so I've removed the 'RFC' tag.  Prove
me wrong by reviewing the patches and sending comments ;-)

The first patch adds the basic driver and TFP410 DVI output.

The second patch adds support for NXP TDA998x family of i2c connected
HDMI encoders.  It is split out into an i2c encoder-slave in case someone
else has hw with the same HDMI encoder.

The final patch adds support for LCD panels, with timings and panel info
coming from device-tree.

The patch set has dependencies on the following patches that I have sent
earlier (which have not changed since then so I am not resending):

 * drm/cma: add debugfs helpers
 * drm: i2c encoder helper wrappers

In addition, the LCD panel patch also depends on Steffen Trumtrar's OF
display helper series. I've moved this patch to last so it can be merged
later if needed.  Although I really see no reason not to merge the OF
display helper series for 3.9.

These patches and their dependencies can also be found here:

  git://people.freedesktop.org/~robclark/linux tilcdc-next
  http://cgit.freedesktop.org/~robclark/linux/log/?h=tilcdc-next

Rob Clark (4):
  drm/tilcdc: add TI LCD Controller DRM driver (v3)
  drm/i2c: nxp-tda998x (v2)
  drm/tilcdc: add encoder slave
  drm/tilcdc: add support for LCD panels (v4)

 drivers/gpu/drm/Kconfig                |   2 +
 drivers/gpu/drm/Makefile               |   1 +
 drivers/gpu/drm/i2c/Makefile           |   3 +
 drivers/gpu/drm/i2c/tda998x_drv.c      | 908 +++++++++++++++++++++++++++++++++
 drivers/gpu/drm/tilcdc/Kconfig         |  25 +
 drivers/gpu/drm/tilcdc/Makefile        |  10 +
 drivers/gpu/drm/tilcdc/tilcdc_crtc.c   | 597 ++++++++++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_drv.c    | 611 ++++++++++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_drv.h    | 159 ++++++
 drivers/gpu/drm/tilcdc/tilcdc_panel.c  | 443 ++++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_panel.h  |  26 +
 drivers/gpu/drm/tilcdc/tilcdc_regs.h   | 154 ++++++
 drivers/gpu/drm/tilcdc/tilcdc_slave.c  | 380 ++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_slave.h  |  26 +
 drivers/gpu/drm/tilcdc/tilcdc_tfp410.c | 423 +++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_tfp410.h |  26 +
 16 files changed, 3794 insertions(+)
 create mode 100644 drivers/gpu/drm/i2c/tda998x_drv.c
 create mode 100644 drivers/gpu/drm/tilcdc/Kconfig
 create mode 100644 drivers/gpu/drm/tilcdc/Makefile
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_crtc.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.h
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_panel.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_panel.h
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_regs.h
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.h
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.h

-- 
1.8.1

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
  2013-01-22 22:36 ` Rob Clark
@ 2013-01-22 22:36   ` Rob Clark
  -1 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-22 22:36 UTC (permalink / raw)
  To: dri-devel; +Cc: Rob Clark, linux-omap, linux-arm-kernel, patches

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.

v1: original
v2: fix fb refcnting and few other cleanups
v3: get +/- vsync/hsync from timings rather than panel-info, add
    option DT max-bandwidth field so driver doesn't attempt to
    pick a display mode with too high memory bandwidth, and other
    small cleanups

Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 drivers/gpu/drm/Kconfig                |   2 +
 drivers/gpu/drm/Makefile               |   1 +
 drivers/gpu/drm/tilcdc/Kconfig         |  10 +
 drivers/gpu/drm/tilcdc/Makefile        |   8 +
 drivers/gpu/drm/tilcdc/tilcdc_crtc.c   | 597 ++++++++++++++++++++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_drv.c    | 605 +++++++++++++++++++++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_drv.h    | 159 +++++++++
 drivers/gpu/drm/tilcdc/tilcdc_regs.h   | 154 +++++++++
 drivers/gpu/drm/tilcdc/tilcdc_tfp410.c | 423 +++++++++++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_tfp410.h |  26 ++
 10 files changed, 1985 insertions(+)
 create mode 100644 drivers/gpu/drm/tilcdc/Kconfig
 create mode 100644 drivers/gpu/drm/tilcdc/Makefile
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_crtc.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.h
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_regs.h
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 983201b..718e042 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/tilcdc/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 6f58c81..3af934d 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_TILCDC)	+= tilcdc/
 obj-y			+= i2c/
diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig
new file mode 100644
index 0000000..ee9b592
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/Kconfig
@@ -0,0 +1,10 @@
+config DRM_TILCDC
+	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/tilcdc/Makefile b/drivers/gpu/drm/tilcdc/Makefile
new file mode 100644
index 0000000..1359cc2
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/Makefile
@@ -0,0 +1,8 @@
+ccflags-y := -Iinclude/drm -Werror
+
+tilcdc-y := \
+	tilcdc_crtc.o \
+	tilcdc_tfp410.o \
+	tilcdc_drv.o
+
+obj-$(CONFIG_DRM_TILCDC)	+= tilcdc.o
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
new file mode 100644
index 0000000..ad7b6b9
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
@@ -0,0 +1,597 @@
+/*
+ * 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/kfifo.h>
+
+#include "tilcdc_drv.h"
+#include "tilcdc_regs.h"
+
+struct tilcdc_crtc {
+	struct drm_crtc base;
+
+	const struct tilcdc_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;
+
+	/* fb currently set to scanout 0/1: */
+	struct drm_framebuffer *scanout[2];
+
+	/* for deferred fb unref's: */
+	DECLARE_KFIFO_PTR(unref_fifo, struct drm_framebuffer *);
+	struct work_struct work;
+};
+#define to_tilcdc_crtc(x) container_of(x, struct tilcdc_crtc, base)
+
+static void unref_worker(struct work_struct *work)
+{
+	struct tilcdc_crtc *tilcdc_crtc = container_of(work, struct tilcdc_crtc, work);
+	struct drm_device *dev = tilcdc_crtc->base.dev;
+	struct drm_framebuffer *fb;
+
+	mutex_lock(&dev->mode_config.mutex);
+	while (kfifo_get(&tilcdc_crtc->unref_fifo, &fb))
+		drm_framebuffer_unreference(fb);
+	mutex_unlock(&dev->mode_config.mutex);
+}
+
+static void set_scanout(struct drm_crtc *crtc, int n)
+{
+	static const uint32_t base_reg[] = {
+			LCDC_DMA_FB_BASE_ADDR_0_REG, LCDC_DMA_FB_BASE_ADDR_1_REG,
+	};
+	static const uint32_t ceil_reg[] = {
+			LCDC_DMA_FB_CEILING_ADDR_0_REG, LCDC_DMA_FB_CEILING_ADDR_1_REG,
+	};
+	static const uint32_t stat[] = {
+			LCDC_END_OF_FRAME0, LCDC_END_OF_FRAME1,
+	};
+	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+
+	pm_runtime_get_sync(dev->dev);
+	tilcdc_write(dev, base_reg[n], tilcdc_crtc->start);
+	tilcdc_write(dev, ceil_reg[n], tilcdc_crtc->end);
+	if (tilcdc_crtc->scanout[n]) {
+		if (kfifo_put(&tilcdc_crtc->unref_fifo,
+				(const struct drm_framebuffer **)&tilcdc_crtc->scanout[n])) {
+			struct tilcdc_drm_private *priv = dev->dev_private;
+			queue_work(priv->wq, &tilcdc_crtc->work);
+		} else {
+			dev_err(dev->dev, "unref fifo full!\n");
+			drm_framebuffer_unreference(tilcdc_crtc->scanout[n]);
+		}
+	}
+	tilcdc_crtc->scanout[n] = crtc->fb;
+	drm_framebuffer_reference(tilcdc_crtc->scanout[n]);
+	tilcdc_crtc->dirty &= ~stat[n];
+	pm_runtime_put_sync(dev->dev);
+}
+
+static void update_scanout(struct drm_crtc *crtc)
+{
+	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_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);
+
+	tilcdc_crtc->start = gem->paddr + fb->offsets[0] +
+			(crtc->y * fb->pitches[0]) + (crtc->x * bpp/8);
+
+	tilcdc_crtc->end = tilcdc_crtc->start +
+			(crtc->mode.vdisplay * fb->pitches[0]);
+
+	if (tilcdc_crtc->dpms == DRM_MODE_DPMS_ON) {
+		/* already enabled, so just mark the frames that need
+		 * updating and they will be updated on vblank:
+		 */
+		tilcdc_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, 0);
+		set_scanout(crtc, 1);
+	}
+}
+
+static void start(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	struct tilcdc_drm_private *priv = dev->dev_private;
+
+	if (priv->rev == 2) {
+		tilcdc_set(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET);
+		msleep(1);
+		tilcdc_clear(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET);
+		msleep(1);
+	}
+
+	tilcdc_set(dev, LCDC_DMA_CTRL_REG, LCDC_DUAL_FRAME_BUFFER_ENABLE);
+	tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_PALETTE_LOAD_MODE(DATA_ONLY));
+	tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
+}
+
+static void stop(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+
+	tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
+}
+
+static void tilcdc_crtc_destroy(struct drm_crtc *crtc)
+{
+	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+
+	WARN_ON(tilcdc_crtc->dpms == DRM_MODE_DPMS_ON);
+
+	drm_crtc_cleanup(crtc);
+	WARN_ON(!kfifo_is_empty(&tilcdc_crtc->unref_fifo));
+	kfifo_free(&tilcdc_crtc->unref_fifo);
+	kfree(tilcdc_crtc);
+}
+
+static int tilcdc_crtc_page_flip(struct drm_crtc *crtc,
+		struct drm_framebuffer *fb,
+		struct drm_pending_vblank_event *event)
+{
+	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+
+	if (tilcdc_crtc->event) {
+		dev_err(dev->dev, "already pending page flip!\n");
+		return -EBUSY;
+	}
+
+	crtc->fb = fb;
+	tilcdc_crtc->event = event;
+	update_scanout(crtc);
+
+	return 0;
+}
+
+static void tilcdc_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+	struct tilcdc_drm_private *priv = dev->dev_private;
+
+	/* we really only care about on or off: */
+	if (mode != DRM_MODE_DPMS_ON)
+		mode = DRM_MODE_DPMS_OFF;
+
+	if (tilcdc_crtc->dpms == mode)
+		return;
+
+	tilcdc_crtc->dpms = mode;
+
+	pm_runtime_get_sync(dev->dev);
+
+	if (mode == DRM_MODE_DPMS_ON) {
+		pm_runtime_forbid(dev->dev);
+		start(crtc);
+	} else {
+		tilcdc_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(
+					tilcdc_crtc->frame_done_wq,
+					tilcdc_crtc->frame_done,
+					msecs_to_jiffies(50));
+			if (ret == 0)
+				dev_err(dev->dev, "timeout waiting for framedone\n");
+		}
+		pm_runtime_allow(dev->dev);
+	}
+
+	pm_runtime_put_sync(dev->dev);
+}
+
+static bool tilcdc_crtc_mode_fixup(struct drm_crtc *crtc,
+		const struct drm_display_mode *mode,
+		struct drm_display_mode *adjusted_mode)
+{
+	return true;
+}
+
+static void tilcdc_crtc_prepare(struct drm_crtc *crtc)
+{
+	tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
+}
+
+static void tilcdc_crtc_commit(struct drm_crtc *crtc)
+{
+	tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
+}
+
+static int tilcdc_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 tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	const struct tilcdc_panel_info *info = tilcdc_crtc->info;
+	uint32_t reg, hbp, hfp, hsw, vbp, vfp, vsw;
+	int ret;
+
+	ret = tilcdc_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 = tilcdc_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);
+	tilcdc_write(dev, LCDC_DMA_CTRL_REG, reg);
+
+	/* Configure the AC Bias Period and Number of Transitions per Interrupt: */
+	reg = tilcdc_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);
+	tilcdc_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;
+
+	DBG("%dx%d, hbp=%u, hfp=%u, hsw=%u, vbp=%u, vfp=%u, vsw=%u",
+			mode->hdisplay, mode->vdisplay, hbp, hfp, hsw, vbp, vfp, vsw);
+
+	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;
+	tilcdc_write(dev, LCDC_RASTER_TIMING_0_REG, reg);
+
+	reg = ((mode->vdisplay - 1) & 0x3ff) |
+		((vbp & 0xff) << 24) |
+		((vfp & 0xff) << 16) |
+		((vsw & 0x3f) << 10);
+	tilcdc_write(dev, LCDC_RASTER_TIMING_1_REG, reg);
+
+	/* Configure display type: */
+	reg = tilcdc_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;
+	tilcdc_write(dev, LCDC_RASTER_CTRL_REG, reg);
+
+	if (info->invert_pxl_clk)
+		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK);
+	else
+		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK);
+
+	if (info->sync_ctrl)
+		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL);
+	else
+		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL);
+
+	if (info->sync_edge)
+		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE);
+	else
+		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE);
+
+	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC);
+	else
+		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC);
+
+	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC);
+	else
+		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC);
+
+	if (info->raster_order)
+		tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER);
+	else
+		tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER);
+
+
+	update_scanout(crtc);
+	tilcdc_crtc_update_clk(crtc);
+
+	pm_runtime_put_sync(dev->dev);
+
+	return 0;
+}
+
+static int tilcdc_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+		struct drm_framebuffer *old_fb)
+{
+	update_scanout(crtc);
+	return 0;
+}
+
+static void tilcdc_crtc_load_lut(struct drm_crtc *crtc)
+{
+}
+
+static const struct drm_crtc_funcs tilcdc_crtc_funcs = {
+		.destroy        = tilcdc_crtc_destroy,
+		.set_config     = drm_crtc_helper_set_config,
+		.page_flip      = tilcdc_crtc_page_flip,
+};
+
+static const struct drm_crtc_helper_funcs tilcdc_crtc_helper_funcs = {
+		.dpms           = tilcdc_crtc_dpms,
+		.mode_fixup     = tilcdc_crtc_mode_fixup,
+		.prepare        = tilcdc_crtc_prepare,
+		.commit         = tilcdc_crtc_commit,
+		.mode_set       = tilcdc_crtc_mode_set,
+		.mode_set_base  = tilcdc_crtc_mode_set_base,
+		.load_lut       = tilcdc_crtc_load_lut,
+};
+
+int tilcdc_crtc_max_width(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	struct tilcdc_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 tilcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode)
+{
+	struct tilcdc_drm_private *priv = crtc->dev->dev_private;
+	unsigned int bandwidth;
+
+	if (mode->hdisplay > tilcdc_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;
+
+	/* filter out modes that would require too much memory bandwidth: */
+	bandwidth = mode->hdisplay * mode->vdisplay * drm_mode_vrefresh(mode);
+	if (bandwidth > priv->max_bandwidth)
+		return MODE_BAD;
+
+	return MODE_OK;
+}
+
+void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc,
+		const struct tilcdc_panel_info *info)
+{
+	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+	tilcdc_crtc->info = info;
+}
+
+void tilcdc_crtc_update_clk(struct drm_crtc *crtc)
+{
+	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	int dpms = tilcdc_crtc->dpms;
+	unsigned int lcd_clk, div;
+	int ret;
+
+	pm_runtime_get_sync(dev->dev);
+
+	if (dpms == DRM_MODE_DPMS_ON)
+		tilcdc_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. */
+	tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) |
+			LCDC_RASTER_MODE);
+
+	if (priv->rev == 2)
+		tilcdc_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)
+		tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
+
+out:
+	pm_runtime_put_sync(dev->dev);
+}
+
+irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc)
+{
+	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	uint32_t stat = tilcdc_read_irqstatus(dev);
+
+	if ((stat & LCDC_SYNC_LOST) && (stat & LCDC_FIFO_UNDERFLOW)) {
+		stop(crtc);
+		dev_err(dev->dev, "error: %08x\n", stat);
+		tilcdc_clear_irqstatus(dev, stat);
+		start(crtc);
+	} else if (stat & LCDC_PL_LOAD_DONE) {
+		tilcdc_clear_irqstatus(dev, stat);
+	} else {
+		struct drm_pending_vblank_event *event;
+		unsigned long flags;
+		uint32_t dirty = tilcdc_crtc->dirty & stat;
+
+		tilcdc_clear_irqstatus(dev, stat);
+
+		if (dirty & LCDC_END_OF_FRAME0)
+			set_scanout(crtc, 0);
+
+		if (dirty & LCDC_END_OF_FRAME1)
+			set_scanout(crtc, 1);
+
+		drm_handle_vblank(dev, 0);
+
+		spin_lock_irqsave(&dev->event_lock, flags);
+		event = tilcdc_crtc->event;
+		tilcdc_crtc->event = NULL;
+		if (event)
+			drm_send_vblank_event(dev, 0, event);
+		spin_unlock_irqrestore(&dev->event_lock, flags);
+
+		if (dirty && !tilcdc_crtc->dirty)
+			drm_vblank_put(dev, 0);
+	}
+
+	if (priv->rev == 2) {
+		if (stat & LCDC_FRAME_DONE) {
+			tilcdc_crtc->frame_done = true;
+			wake_up(&tilcdc_crtc->frame_done_wq);
+		}
+		tilcdc_write(dev, LCDC_END_OF_INT_IND_REG, 0);
+	}
+
+	return IRQ_HANDLED;
+}
+
+void tilcdc_crtc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file)
+{
+	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_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 = tilcdc_crtc->event;
+	if (event && event->base.file_priv == file) {
+		tilcdc_crtc->event = NULL;
+		event->base.destroy(&event->base);
+		drm_vblank_put(dev, 0);
+	}
+	spin_unlock_irqrestore(&dev->event_lock, flags);
+}
+
+struct drm_crtc *tilcdc_crtc_create(struct drm_device *dev)
+{
+	struct tilcdc_crtc *tilcdc_crtc;
+	struct drm_crtc *crtc;
+	int ret;
+
+	tilcdc_crtc = kzalloc(sizeof(*tilcdc_crtc), GFP_KERNEL);
+	if (!tilcdc_crtc) {
+		dev_err(dev->dev, "allocation failed\n");
+		return NULL;
+	}
+
+	crtc = &tilcdc_crtc->base;
+
+	tilcdc_crtc->dpms = DRM_MODE_DPMS_OFF;
+	init_waitqueue_head(&tilcdc_crtc->frame_done_wq);
+
+	ret = kfifo_alloc(&tilcdc_crtc->unref_fifo, 16, GFP_KERNEL);
+	if (ret) {
+		dev_err(dev->dev, "could not allocate unref FIFO\n");
+		goto fail;
+	}
+
+	INIT_WORK(&tilcdc_crtc->work, unref_worker);
+
+	ret = drm_crtc_init(dev, crtc, &tilcdc_crtc_funcs);
+	if (ret < 0)
+		goto fail;
+
+	drm_crtc_helper_add(crtc, &tilcdc_crtc_helper_funcs);
+
+	return crtc;
+
+fail:
+	tilcdc_crtc_destroy(crtc);
+	return NULL;
+}
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
new file mode 100644
index 0000000..cf1fddc
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
@@ -0,0 +1,605 @@
+/*
+ * 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 "tilcdc_drv.h"
+#include "tilcdc_regs.h"
+#include "tilcdc_tfp410.h"
+
+#include "drm_fb_helper.h"
+
+static LIST_HEAD(module_list);
+
+void tilcdc_module_init(struct tilcdc_module *mod, const char *name,
+		const struct tilcdc_module_ops *funcs)
+{
+	mod->name = name;
+	mod->funcs = funcs;
+	INIT_LIST_HEAD(&mod->list);
+	list_add(&mod->list, &module_list);
+}
+
+void tilcdc_module_cleanup(struct tilcdc_module *mod)
+{
+	list_del(&mod->list);
+}
+
+static struct of_device_id tilcdc_of_match[];
+
+static struct drm_framebuffer *tilcdc_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 tilcdc_fb_output_poll_changed(struct drm_device *dev)
+{
+	struct tilcdc_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 = tilcdc_fb_create,
+	.output_poll_changed = tilcdc_fb_output_poll_changed,
+};
+
+static int modeset_init(struct drm_device *dev)
+{
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	struct tilcdc_module *mod;
+
+	drm_mode_config_init(dev);
+
+	priv->crtc = tilcdc_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 = tilcdc_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 tilcdc_drm_private *priv = container_of(nb,
+			struct tilcdc_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);
+			tilcdc_crtc_update_clk(priv->crtc);
+		}
+	}
+
+	return 0;
+}
+#endif
+
+/*
+ * DRM operations:
+ */
+
+static int tilcdc_unload(struct drm_device *dev)
+{
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	struct tilcdc_module *mod, *cur;
+
+	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);
+
+	flush_workqueue(priv->wq);
+	destroy_workqueue(priv->wq);
+
+	dev->dev_private = NULL;
+
+	pm_runtime_disable(dev->dev);
+
+	list_for_each_entry_safe(mod, cur, &module_list, list) {
+		DBG("destroying module: %s", mod->name);
+		mod->funcs->destroy(mod);
+	}
+
+	kfree(priv);
+
+	return 0;
+}
+
+static int tilcdc_load(struct drm_device *dev, unsigned long flags)
+{
+	struct platform_device *pdev = dev->platformdev;
+	struct device_node *node = pdev->dev.of_node;
+	struct tilcdc_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;
+
+	priv->wq = alloc_ordered_workqueue("tilcdc", 0);
+
+	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
+
+	if (of_property_read_u32(node, "max-bandwidth", &priv->max_bandwidth))
+		priv->max_bandwidth = 1280 * 1024 * 60;
+
+	pm_runtime_enable(dev->dev);
+
+	/* Determine LCD IP Version */
+	pm_runtime_get_sync(dev->dev);
+	switch (tilcdc_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",
+				tilcdc_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:
+	tilcdc_unload(dev);
+	return ret;
+}
+
+static void tilcdc_preclose(struct drm_device *dev, struct drm_file *file)
+{
+	struct tilcdc_drm_private *priv = dev->dev_private;
+
+	tilcdc_crtc_cancel_page_flip(priv->crtc, file);
+}
+
+static void tilcdc_lastclose(struct drm_device *dev)
+{
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	drm_fbdev_cma_restore_mode(priv->fbdev);
+}
+
+static irqreturn_t tilcdc_irq(DRM_IRQ_ARGS)
+{
+	struct drm_device *dev = arg;
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	return tilcdc_crtc_irq(priv->crtc);
+}
+
+static void tilcdc_irq_preinstall(struct drm_device *dev)
+{
+	tilcdc_clear_irqstatus(dev, 0xffffffff);
+}
+
+static int tilcdc_irq_postinstall(struct drm_device *dev)
+{
+	struct tilcdc_drm_private *priv = dev->dev_private;
+
+	/* enable FIFO underflow irq: */
+	if (priv->rev == 1) {
+		tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_V1_UNDERFLOW_INT_ENA);
+	} else {
+		tilcdc_set(dev, LCDC_INT_ENABLE_SET_REG, LCDC_V2_UNDERFLOW_INT_ENA);
+	}
+
+	return 0;
+}
+
+static void tilcdc_irq_uninstall(struct drm_device *dev)
+{
+	struct tilcdc_drm_private *priv = dev->dev_private;
+
+	/* disable irqs that we might have enabled: */
+	if (priv->rev == 1) {
+		tilcdc_clear(dev, LCDC_RASTER_CTRL_REG,
+				LCDC_V1_UNDERFLOW_INT_ENA | LCDC_V1_PL_INT_ENA);
+		tilcdc_clear(dev, LCDC_DMA_CTRL_REG, LCDC_V1_END_OF_FRAME_INT_ENA);
+	} else {
+		tilcdc_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 tilcdc_drm_private *priv = dev->dev_private;
+	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;
+	}
+
+	if (enable)
+		tilcdc_set(dev, reg, mask);
+	else
+		tilcdc_clear(dev, reg, mask);
+}
+
+static int tilcdc_enable_vblank(struct drm_device *dev, int crtc)
+{
+	enable_vblank(dev, true);
+	return 0;
+}
+
+static void tilcdc_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_FB_BASE_ADDR_0_REG),
+		REG(1, true,  LCDC_DMA_FB_CEILING_ADDR_0_REG),
+		REG(1, true,  LCDC_DMA_FB_BASE_ADDR_1_REG),
+		REG(1, true,  LCDC_DMA_FB_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 tilcdc_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 tilcdc_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,
+					tilcdc_read(dev, registers[i].reg));
+
+	pm_runtime_put_sync(dev->dev);
+
+	return 0;
+}
+
+static int tilcdc_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 tilcdc_debugfs_list[] = {
+		{ "regs", tilcdc_regs_show, 0 },
+		{ "mm",   tilcdc_mm_show,   0 },
+		{ "fb",   drm_fb_cma_debugfs_show, 0 },
+};
+
+static int tilcdc_debugfs_init(struct drm_minor *minor)
+{
+	struct drm_device *dev = minor->dev;
+	struct tilcdc_module *mod;
+	int ret;
+
+	ret = drm_debugfs_create_files(tilcdc_debugfs_list,
+			ARRAY_SIZE(tilcdc_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 tilcdc_debugfs_list\n");
+		return ret;
+	}
+
+	return ret;
+}
+
+static void tilcdc_debugfs_cleanup(struct drm_minor *minor)
+{
+	struct tilcdc_module *mod;
+	drm_debugfs_remove_files(tilcdc_debugfs_list,
+			ARRAY_SIZE(tilcdc_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 tilcdc_driver = {
+	.driver_features    = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET,
+	.load               = tilcdc_load,
+	.unload             = tilcdc_unload,
+	.preclose           = tilcdc_preclose,
+	.lastclose          = tilcdc_lastclose,
+	.irq_handler        = tilcdc_irq,
+	.irq_preinstall     = tilcdc_irq_preinstall,
+	.irq_postinstall    = tilcdc_irq_postinstall,
+	.irq_uninstall      = tilcdc_irq_uninstall,
+	.get_vblank_counter = drm_vblank_count,
+	.enable_vblank      = tilcdc_enable_vblank,
+	.disable_vblank     = tilcdc_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       = tilcdc_debugfs_init,
+	.debugfs_cleanup    = tilcdc_debugfs_cleanup,
+#endif
+	.fops               = &fops,
+	.name               = "tilcdc",
+	.desc               = "TI LCD Controller DRM",
+	.date               = "20121205",
+	.major              = 1,
+	.minor              = 0,
+};
+
+/*
+ * Power management:
+ */
+
+#if CONFIG_PM_SLEEP
+static int tilcdc_pm_suspend(struct device *dev)
+{
+	struct drm_device *ddev = dev_get_drvdata(dev);
+	struct tilcdc_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++] = tilcdc_read(ddev, registers[i].reg);
+
+	return 0;
+}
+
+static int tilcdc_pm_resume(struct device *dev)
+{
+	struct drm_device *ddev = dev_get_drvdata(dev);
+	struct tilcdc_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))
+			tilcdc_write(ddev, registers[i].reg, priv->saved_register[n++]);
+
+	drm_kms_helper_poll_enable(ddev);
+
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops tilcdc_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(tilcdc_pm_suspend, tilcdc_pm_resume)
+};
+
+/*
+ * Platform driver:
+ */
+
+static int tilcdc_pdev_probe(struct platform_device *pdev)
+{
+	/* bail out early if no DT data: */
+	if (!pdev->dev.of_node) {
+		dev_err(&pdev->dev, "device-tree data is missing\n");
+		return -ENXIO;
+	}
+
+	return drm_platform_init(&tilcdc_driver, pdev);
+}
+
+static int tilcdc_pdev_remove(struct platform_device *pdev)
+{
+	drm_platform_exit(&tilcdc_driver, pdev);
+
+	return 0;
+}
+
+static struct of_device_id tilcdc_of_match[] = {
+		{ .compatible = "ti,am33xx-tilcdc", },
+		{ },
+};
+MODULE_DEVICE_TABLE(of, tilcdc_of_match);
+
+static struct platform_driver tilcdc_platform_driver = {
+	.probe      = tilcdc_pdev_probe,
+	.remove     = tilcdc_pdev_remove,
+	.driver     = {
+		.owner  = THIS_MODULE,
+		.name   = "tilcdc",
+		.pm     = &tilcdc_pm_ops,
+		.of_match_table = tilcdc_of_match,
+	},
+};
+
+static int __init tilcdc_drm_init(void)
+{
+	DBG("init");
+	tilcdc_tfp410_init();
+	return platform_driver_register(&tilcdc_platform_driver);
+}
+
+static void __exit tilcdc_drm_fini(void)
+{
+	DBG("fini");
+	tilcdc_tfp410_fini();
+	platform_driver_unregister(&tilcdc_platform_driver);
+}
+
+module_init(tilcdc_drm_init);
+module_exit(tilcdc_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/tilcdc/tilcdc_drv.h b/drivers/gpu/drm/tilcdc/tilcdc_drv.h
new file mode 100644
index 0000000..76321cd
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.h
@@ -0,0 +1,159 @@
+/*
+ * 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 __TILCDC_DRV_H__
+#define __TILCDC_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 tilcdc_drm_private {
+	void __iomem *mmio;
+
+	struct clk *disp_clk;    /* display dpll */
+	struct clk *clk;         /* functional clock */
+	int rev;                 /* IP revision */
+
+	/* don't attempt resolutions w/ higher W * H * Hz: */
+	uint32_t max_bandwidth;
+
+	/* register contents saved across suspend/resume: */
+	u32 saved_register[12];
+
+#ifdef CONFIG_CPU_FREQ
+	struct notifier_block freq_transition;
+	unsigned int lcd_fck_rate;
+#endif
+
+	struct workqueue_struct *wq;
+
+	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 tilcdc_register_module().
+ */
+struct tilcdc_module;
+
+struct tilcdc_module_ops {
+	/* create appropriate encoders/connectors: */
+	int (*modeset_init)(struct tilcdc_module *mod, struct drm_device *dev);
+	void (*destroy)(struct tilcdc_module *mod);
+#ifdef CONFIG_DEBUG_FS
+	/* create debugfs nodes (can be NULL): */
+	int (*debugfs_init)(struct tilcdc_module *mod, struct drm_minor *minor);
+	/* cleanup debugfs nodes (can be NULL): */
+	void (*debugfs_cleanup)(struct tilcdc_module *mod, struct drm_minor *minor);
+#endif
+};
+
+struct tilcdc_module {
+	const char *name;
+	struct list_head list;
+	const struct tilcdc_module_ops *funcs;
+};
+
+void tilcdc_module_init(struct tilcdc_module *mod, const char *name,
+		const struct tilcdc_module_ops *funcs);
+void tilcdc_module_cleanup(struct tilcdc_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
+ * tilcdc_crtc_set_panel_info() to set this during modeset.
+ */
+struct tilcdc_panel_info {
+
+	uint32_t max_bpp;
+	uint32_t min_bpp;
+
+	/* AC Bias Pin Frequency */
+	uint32_t ac_bias;
+
+	/* AC Bias Pin Transitions per Interrupt */
+	uint32_t ac_bias_intrpt;
+
+	/* DMA burst size */
+	uint32_t dma_burst_sz;
+
+	/* Bits per pixel */
+	uint32_t bpp;
+
+	/* FIFO DMA Request Delay */
+	uint32_t fdd;
+
+	/* TFT Alternative Signal Mapping (Only for active) */
+	bool tft_alt_mode;
+
+	/* 12 Bit Per Pixel (5-6-5) Mode (Only for passive) */
+	bool stn_565_mode;
+
+	/* Mono 8-bit Mode: 1=D0-D7 or 0=D0-D3 */
+	bool mono_8bit_mode;
+
+	/* Invert pixel clock */
+	bool invert_pxl_clk;
+
+	/* Horizontal and Vertical Sync Edge: 0=rising 1=falling */
+	uint32_t sync_edge;
+
+	/* Horizontal and Vertical Sync: Control: 0=ignore */
+	uint32_t sync_ctrl;
+
+	/* Raster Data Order Select: 1=Most-to-least 0=Least-to-most */
+	uint32_t raster_order;
+
+	/* DMA FIFO threshold */
+	uint32_t fifo_th;
+};
+
+#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
+
+struct drm_crtc *tilcdc_crtc_create(struct drm_device *dev);
+void tilcdc_crtc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file);
+irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc);
+void tilcdc_crtc_update_clk(struct drm_crtc *crtc);
+void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc,
+		const struct tilcdc_panel_info *info);
+int tilcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode);
+int tilcdc_crtc_max_width(struct drm_crtc *crtc);
+
+#endif /* __TILCDC_DRV_H__ */
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_regs.h b/drivers/gpu/drm/tilcdc/tilcdc_regs.h
new file mode 100644
index 0000000..17fd1b4
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_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 __TILCDC_REGS_H__
+#define __TILCDC_REGS_H__
+
+/* LCDC register definitions, based on da8xx-fb */
+
+#include <linux/bitops.h>
+
+#include "tilcdc_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_HSYNC                        BIT(21)
+#define LCDC_INVERT_VSYNC                        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_FB_BASE_ADDR_0_REG              0x44
+#define LCDC_DMA_FB_CEILING_ADDR_0_REG           0x48
+#define LCDC_DMA_FB_BASE_ADDR_1_REG              0x4c
+#define LCDC_DMA_FB_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 tilcdc_write(struct drm_device *dev, u32 reg, u32 data)
+{
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	iowrite32(data, priv->mmio + reg);
+}
+
+static inline u32 tilcdc_read(struct drm_device *dev, u32 reg)
+{
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	return ioread32(priv->mmio + reg);
+}
+
+static inline void tilcdc_set(struct drm_device *dev, u32 reg, u32 mask)
+{
+	tilcdc_write(dev, reg, tilcdc_read(dev, reg) | mask);
+}
+
+static inline void tilcdc_clear(struct drm_device *dev, u32 reg, u32 mask)
+{
+	tilcdc_write(dev, reg, tilcdc_read(dev, reg) & ~mask);
+}
+
+/* the register to read/clear irqstatus differs between v1 and v2 of the IP */
+static inline u32 tilcdc_irqstatus_reg(struct drm_device *dev)
+{
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	return (priv->rev == 2) ? LCDC_MASKED_STAT_REG : LCDC_STAT_REG;
+}
+
+static inline u32 tilcdc_read_irqstatus(struct drm_device *dev)
+{
+	return tilcdc_read(dev, tilcdc_irqstatus_reg(dev));
+}
+
+static inline void tilcdc_clear_irqstatus(struct drm_device *dev, u32 mask)
+{
+	tilcdc_write(dev, tilcdc_irqstatus_reg(dev), mask);
+}
+
+#endif /* __TILCDC_REGS_H__ */
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_tfp410.c b/drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
new file mode 100644
index 0000000..eaae4dd
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
@@ -0,0 +1,423 @@
+/*
+ * 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 "tilcdc_drv.h"
+
+struct tfp410_module {
+	struct tilcdc_module base;
+	struct i2c_adapter *i2c;
+	int gpio;
+};
+#define to_tfp410_module(x) container_of(x, struct tfp410_module, base)
+
+
+static const struct tilcdc_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,
+		.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);
+	tilcdc_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 tilcdc_drm_private *priv = connector->dev->dev_private;
+	/* our only constraints are what the crtc can generate: */
+	return tilcdc_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 tilcdc_module *mod, struct drm_device *dev)
+{
+	struct tfp410_module *tfp410_mod = to_tfp410_module(mod);
+	struct tilcdc_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 tilcdc_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);
+
+	tilcdc_module_cleanup(mod);
+	kfree(tfp410_mod);
+}
+
+static const struct tilcdc_module_ops tfp410_module_ops = {
+		.modeset_init = tfp410_modeset_init,
+		.destroy = tfp410_destroy,
+};
+
+/*
+ * Device:
+ */
+
+static struct of_device_id tfp410_of_match[];
+
+static int 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 tilcdc_module *mod;
+	struct pinctrl *pinctrl;
+	uint32_t i2c_phandle;
+	int ret = -EINVAL;
+
+	/* bail out early if no DT data: */
+	if (!node) {
+		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;
+
+	tilcdc_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 tfp410_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static struct of_device_id tfp410_of_match[] = {
+		{ .compatible = "tilcdc,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 tilcdc_tfp410_init(void)
+{
+	return platform_driver_register(&tfp410_driver);
+}
+
+void __exit tilcdc_tfp410_fini(void)
+{
+	platform_driver_unregister(&tfp410_driver);
+}
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_tfp410.h b/drivers/gpu/drm/tilcdc/tilcdc_tfp410.h
new file mode 100644
index 0000000..5b800f1
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_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 __TILCDC_TFP410_H__
+#define __TILCDC_TFP410_H__
+
+/* sub-module for tfp410 dvi adaptor */
+
+int tilcdc_tfp410_init(void);
+void tilcdc_tfp410_fini(void);
+
+#endif /* __TILCDC_TFP410_H__ */
-- 
1.8.1

^ permalink raw reply related	[flat|nested] 66+ messages in thread

* [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-22 22:36   ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-22 22:36 UTC (permalink / raw)
  To: linux-arm-kernel

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.

v1: original
v2: fix fb refcnting and few other cleanups
v3: get +/- vsync/hsync from timings rather than panel-info, add
    option DT max-bandwidth field so driver doesn't attempt to
    pick a display mode with too high memory bandwidth, and other
    small cleanups

Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 drivers/gpu/drm/Kconfig                |   2 +
 drivers/gpu/drm/Makefile               |   1 +
 drivers/gpu/drm/tilcdc/Kconfig         |  10 +
 drivers/gpu/drm/tilcdc/Makefile        |   8 +
 drivers/gpu/drm/tilcdc/tilcdc_crtc.c   | 597 ++++++++++++++++++++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_drv.c    | 605 +++++++++++++++++++++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_drv.h    | 159 +++++++++
 drivers/gpu/drm/tilcdc/tilcdc_regs.h   | 154 +++++++++
 drivers/gpu/drm/tilcdc/tilcdc_tfp410.c | 423 +++++++++++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_tfp410.h |  26 ++
 10 files changed, 1985 insertions(+)
 create mode 100644 drivers/gpu/drm/tilcdc/Kconfig
 create mode 100644 drivers/gpu/drm/tilcdc/Makefile
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_crtc.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.h
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_regs.h
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 983201b..718e042 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/tilcdc/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 6f58c81..3af934d 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_TILCDC)	+= tilcdc/
 obj-y			+= i2c/
diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig
new file mode 100644
index 0000000..ee9b592
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/Kconfig
@@ -0,0 +1,10 @@
+config DRM_TILCDC
+	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/tilcdc/Makefile b/drivers/gpu/drm/tilcdc/Makefile
new file mode 100644
index 0000000..1359cc2
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/Makefile
@@ -0,0 +1,8 @@
+ccflags-y := -Iinclude/drm -Werror
+
+tilcdc-y := \
+	tilcdc_crtc.o \
+	tilcdc_tfp410.o \
+	tilcdc_drv.o
+
+obj-$(CONFIG_DRM_TILCDC)	+= tilcdc.o
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_crtc.c b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
new file mode 100644
index 0000000..ad7b6b9
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_crtc.c
@@ -0,0 +1,597 @@
+/*
+ * 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/kfifo.h>
+
+#include "tilcdc_drv.h"
+#include "tilcdc_regs.h"
+
+struct tilcdc_crtc {
+	struct drm_crtc base;
+
+	const struct tilcdc_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;
+
+	/* fb currently set to scanout 0/1: */
+	struct drm_framebuffer *scanout[2];
+
+	/* for deferred fb unref's: */
+	DECLARE_KFIFO_PTR(unref_fifo, struct drm_framebuffer *);
+	struct work_struct work;
+};
+#define to_tilcdc_crtc(x) container_of(x, struct tilcdc_crtc, base)
+
+static void unref_worker(struct work_struct *work)
+{
+	struct tilcdc_crtc *tilcdc_crtc = container_of(work, struct tilcdc_crtc, work);
+	struct drm_device *dev = tilcdc_crtc->base.dev;
+	struct drm_framebuffer *fb;
+
+	mutex_lock(&dev->mode_config.mutex);
+	while (kfifo_get(&tilcdc_crtc->unref_fifo, &fb))
+		drm_framebuffer_unreference(fb);
+	mutex_unlock(&dev->mode_config.mutex);
+}
+
+static void set_scanout(struct drm_crtc *crtc, int n)
+{
+	static const uint32_t base_reg[] = {
+			LCDC_DMA_FB_BASE_ADDR_0_REG, LCDC_DMA_FB_BASE_ADDR_1_REG,
+	};
+	static const uint32_t ceil_reg[] = {
+			LCDC_DMA_FB_CEILING_ADDR_0_REG, LCDC_DMA_FB_CEILING_ADDR_1_REG,
+	};
+	static const uint32_t stat[] = {
+			LCDC_END_OF_FRAME0, LCDC_END_OF_FRAME1,
+	};
+	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+
+	pm_runtime_get_sync(dev->dev);
+	tilcdc_write(dev, base_reg[n], tilcdc_crtc->start);
+	tilcdc_write(dev, ceil_reg[n], tilcdc_crtc->end);
+	if (tilcdc_crtc->scanout[n]) {
+		if (kfifo_put(&tilcdc_crtc->unref_fifo,
+				(const struct drm_framebuffer **)&tilcdc_crtc->scanout[n])) {
+			struct tilcdc_drm_private *priv = dev->dev_private;
+			queue_work(priv->wq, &tilcdc_crtc->work);
+		} else {
+			dev_err(dev->dev, "unref fifo full!\n");
+			drm_framebuffer_unreference(tilcdc_crtc->scanout[n]);
+		}
+	}
+	tilcdc_crtc->scanout[n] = crtc->fb;
+	drm_framebuffer_reference(tilcdc_crtc->scanout[n]);
+	tilcdc_crtc->dirty &= ~stat[n];
+	pm_runtime_put_sync(dev->dev);
+}
+
+static void update_scanout(struct drm_crtc *crtc)
+{
+	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_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);
+
+	tilcdc_crtc->start = gem->paddr + fb->offsets[0] +
+			(crtc->y * fb->pitches[0]) + (crtc->x * bpp/8);
+
+	tilcdc_crtc->end = tilcdc_crtc->start +
+			(crtc->mode.vdisplay * fb->pitches[0]);
+
+	if (tilcdc_crtc->dpms == DRM_MODE_DPMS_ON) {
+		/* already enabled, so just mark the frames that need
+		 * updating and they will be updated on vblank:
+		 */
+		tilcdc_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, 0);
+		set_scanout(crtc, 1);
+	}
+}
+
+static void start(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	struct tilcdc_drm_private *priv = dev->dev_private;
+
+	if (priv->rev == 2) {
+		tilcdc_set(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET);
+		msleep(1);
+		tilcdc_clear(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET);
+		msleep(1);
+	}
+
+	tilcdc_set(dev, LCDC_DMA_CTRL_REG, LCDC_DUAL_FRAME_BUFFER_ENABLE);
+	tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_PALETTE_LOAD_MODE(DATA_ONLY));
+	tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
+}
+
+static void stop(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+
+	tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
+}
+
+static void tilcdc_crtc_destroy(struct drm_crtc *crtc)
+{
+	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+
+	WARN_ON(tilcdc_crtc->dpms == DRM_MODE_DPMS_ON);
+
+	drm_crtc_cleanup(crtc);
+	WARN_ON(!kfifo_is_empty(&tilcdc_crtc->unref_fifo));
+	kfifo_free(&tilcdc_crtc->unref_fifo);
+	kfree(tilcdc_crtc);
+}
+
+static int tilcdc_crtc_page_flip(struct drm_crtc *crtc,
+		struct drm_framebuffer *fb,
+		struct drm_pending_vblank_event *event)
+{
+	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+
+	if (tilcdc_crtc->event) {
+		dev_err(dev->dev, "already pending page flip!\n");
+		return -EBUSY;
+	}
+
+	crtc->fb = fb;
+	tilcdc_crtc->event = event;
+	update_scanout(crtc);
+
+	return 0;
+}
+
+static void tilcdc_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+	struct tilcdc_drm_private *priv = dev->dev_private;
+
+	/* we really only care about on or off: */
+	if (mode != DRM_MODE_DPMS_ON)
+		mode = DRM_MODE_DPMS_OFF;
+
+	if (tilcdc_crtc->dpms == mode)
+		return;
+
+	tilcdc_crtc->dpms = mode;
+
+	pm_runtime_get_sync(dev->dev);
+
+	if (mode == DRM_MODE_DPMS_ON) {
+		pm_runtime_forbid(dev->dev);
+		start(crtc);
+	} else {
+		tilcdc_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(
+					tilcdc_crtc->frame_done_wq,
+					tilcdc_crtc->frame_done,
+					msecs_to_jiffies(50));
+			if (ret == 0)
+				dev_err(dev->dev, "timeout waiting for framedone\n");
+		}
+		pm_runtime_allow(dev->dev);
+	}
+
+	pm_runtime_put_sync(dev->dev);
+}
+
+static bool tilcdc_crtc_mode_fixup(struct drm_crtc *crtc,
+		const struct drm_display_mode *mode,
+		struct drm_display_mode *adjusted_mode)
+{
+	return true;
+}
+
+static void tilcdc_crtc_prepare(struct drm_crtc *crtc)
+{
+	tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
+}
+
+static void tilcdc_crtc_commit(struct drm_crtc *crtc)
+{
+	tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
+}
+
+static int tilcdc_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 tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	const struct tilcdc_panel_info *info = tilcdc_crtc->info;
+	uint32_t reg, hbp, hfp, hsw, vbp, vfp, vsw;
+	int ret;
+
+	ret = tilcdc_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 = tilcdc_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);
+	tilcdc_write(dev, LCDC_DMA_CTRL_REG, reg);
+
+	/* Configure the AC Bias Period and Number of Transitions per Interrupt: */
+	reg = tilcdc_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);
+	tilcdc_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;
+
+	DBG("%dx%d, hbp=%u, hfp=%u, hsw=%u, vbp=%u, vfp=%u, vsw=%u",
+			mode->hdisplay, mode->vdisplay, hbp, hfp, hsw, vbp, vfp, vsw);
+
+	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;
+	tilcdc_write(dev, LCDC_RASTER_TIMING_0_REG, reg);
+
+	reg = ((mode->vdisplay - 1) & 0x3ff) |
+		((vbp & 0xff) << 24) |
+		((vfp & 0xff) << 16) |
+		((vsw & 0x3f) << 10);
+	tilcdc_write(dev, LCDC_RASTER_TIMING_1_REG, reg);
+
+	/* Configure display type: */
+	reg = tilcdc_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;
+	tilcdc_write(dev, LCDC_RASTER_CTRL_REG, reg);
+
+	if (info->invert_pxl_clk)
+		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK);
+	else
+		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK);
+
+	if (info->sync_ctrl)
+		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL);
+	else
+		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL);
+
+	if (info->sync_edge)
+		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE);
+	else
+		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE);
+
+	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC);
+	else
+		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC);
+
+	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC);
+	else
+		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC);
+
+	if (info->raster_order)
+		tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER);
+	else
+		tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER);
+
+
+	update_scanout(crtc);
+	tilcdc_crtc_update_clk(crtc);
+
+	pm_runtime_put_sync(dev->dev);
+
+	return 0;
+}
+
+static int tilcdc_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
+		struct drm_framebuffer *old_fb)
+{
+	update_scanout(crtc);
+	return 0;
+}
+
+static void tilcdc_crtc_load_lut(struct drm_crtc *crtc)
+{
+}
+
+static const struct drm_crtc_funcs tilcdc_crtc_funcs = {
+		.destroy        = tilcdc_crtc_destroy,
+		.set_config     = drm_crtc_helper_set_config,
+		.page_flip      = tilcdc_crtc_page_flip,
+};
+
+static const struct drm_crtc_helper_funcs tilcdc_crtc_helper_funcs = {
+		.dpms           = tilcdc_crtc_dpms,
+		.mode_fixup     = tilcdc_crtc_mode_fixup,
+		.prepare        = tilcdc_crtc_prepare,
+		.commit         = tilcdc_crtc_commit,
+		.mode_set       = tilcdc_crtc_mode_set,
+		.mode_set_base  = tilcdc_crtc_mode_set_base,
+		.load_lut       = tilcdc_crtc_load_lut,
+};
+
+int tilcdc_crtc_max_width(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	struct tilcdc_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 tilcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode)
+{
+	struct tilcdc_drm_private *priv = crtc->dev->dev_private;
+	unsigned int bandwidth;
+
+	if (mode->hdisplay > tilcdc_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;
+
+	/* filter out modes that would require too much memory bandwidth: */
+	bandwidth = mode->hdisplay * mode->vdisplay * drm_mode_vrefresh(mode);
+	if (bandwidth > priv->max_bandwidth)
+		return MODE_BAD;
+
+	return MODE_OK;
+}
+
+void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc,
+		const struct tilcdc_panel_info *info)
+{
+	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+	tilcdc_crtc->info = info;
+}
+
+void tilcdc_crtc_update_clk(struct drm_crtc *crtc)
+{
+	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	int dpms = tilcdc_crtc->dpms;
+	unsigned int lcd_clk, div;
+	int ret;
+
+	pm_runtime_get_sync(dev->dev);
+
+	if (dpms == DRM_MODE_DPMS_ON)
+		tilcdc_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. */
+	tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) |
+			LCDC_RASTER_MODE);
+
+	if (priv->rev == 2)
+		tilcdc_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)
+		tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
+
+out:
+	pm_runtime_put_sync(dev->dev);
+}
+
+irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc)
+{
+	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
+	struct drm_device *dev = crtc->dev;
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	uint32_t stat = tilcdc_read_irqstatus(dev);
+
+	if ((stat & LCDC_SYNC_LOST) && (stat & LCDC_FIFO_UNDERFLOW)) {
+		stop(crtc);
+		dev_err(dev->dev, "error: %08x\n", stat);
+		tilcdc_clear_irqstatus(dev, stat);
+		start(crtc);
+	} else if (stat & LCDC_PL_LOAD_DONE) {
+		tilcdc_clear_irqstatus(dev, stat);
+	} else {
+		struct drm_pending_vblank_event *event;
+		unsigned long flags;
+		uint32_t dirty = tilcdc_crtc->dirty & stat;
+
+		tilcdc_clear_irqstatus(dev, stat);
+
+		if (dirty & LCDC_END_OF_FRAME0)
+			set_scanout(crtc, 0);
+
+		if (dirty & LCDC_END_OF_FRAME1)
+			set_scanout(crtc, 1);
+
+		drm_handle_vblank(dev, 0);
+
+		spin_lock_irqsave(&dev->event_lock, flags);
+		event = tilcdc_crtc->event;
+		tilcdc_crtc->event = NULL;
+		if (event)
+			drm_send_vblank_event(dev, 0, event);
+		spin_unlock_irqrestore(&dev->event_lock, flags);
+
+		if (dirty && !tilcdc_crtc->dirty)
+			drm_vblank_put(dev, 0);
+	}
+
+	if (priv->rev == 2) {
+		if (stat & LCDC_FRAME_DONE) {
+			tilcdc_crtc->frame_done = true;
+			wake_up(&tilcdc_crtc->frame_done_wq);
+		}
+		tilcdc_write(dev, LCDC_END_OF_INT_IND_REG, 0);
+	}
+
+	return IRQ_HANDLED;
+}
+
+void tilcdc_crtc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file)
+{
+	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_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 = tilcdc_crtc->event;
+	if (event && event->base.file_priv == file) {
+		tilcdc_crtc->event = NULL;
+		event->base.destroy(&event->base);
+		drm_vblank_put(dev, 0);
+	}
+	spin_unlock_irqrestore(&dev->event_lock, flags);
+}
+
+struct drm_crtc *tilcdc_crtc_create(struct drm_device *dev)
+{
+	struct tilcdc_crtc *tilcdc_crtc;
+	struct drm_crtc *crtc;
+	int ret;
+
+	tilcdc_crtc = kzalloc(sizeof(*tilcdc_crtc), GFP_KERNEL);
+	if (!tilcdc_crtc) {
+		dev_err(dev->dev, "allocation failed\n");
+		return NULL;
+	}
+
+	crtc = &tilcdc_crtc->base;
+
+	tilcdc_crtc->dpms = DRM_MODE_DPMS_OFF;
+	init_waitqueue_head(&tilcdc_crtc->frame_done_wq);
+
+	ret = kfifo_alloc(&tilcdc_crtc->unref_fifo, 16, GFP_KERNEL);
+	if (ret) {
+		dev_err(dev->dev, "could not allocate unref FIFO\n");
+		goto fail;
+	}
+
+	INIT_WORK(&tilcdc_crtc->work, unref_worker);
+
+	ret = drm_crtc_init(dev, crtc, &tilcdc_crtc_funcs);
+	if (ret < 0)
+		goto fail;
+
+	drm_crtc_helper_add(crtc, &tilcdc_crtc_helper_funcs);
+
+	return crtc;
+
+fail:
+	tilcdc_crtc_destroy(crtc);
+	return NULL;
+}
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
new file mode 100644
index 0000000..cf1fddc
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
@@ -0,0 +1,605 @@
+/*
+ * 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 "tilcdc_drv.h"
+#include "tilcdc_regs.h"
+#include "tilcdc_tfp410.h"
+
+#include "drm_fb_helper.h"
+
+static LIST_HEAD(module_list);
+
+void tilcdc_module_init(struct tilcdc_module *mod, const char *name,
+		const struct tilcdc_module_ops *funcs)
+{
+	mod->name = name;
+	mod->funcs = funcs;
+	INIT_LIST_HEAD(&mod->list);
+	list_add(&mod->list, &module_list);
+}
+
+void tilcdc_module_cleanup(struct tilcdc_module *mod)
+{
+	list_del(&mod->list);
+}
+
+static struct of_device_id tilcdc_of_match[];
+
+static struct drm_framebuffer *tilcdc_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 tilcdc_fb_output_poll_changed(struct drm_device *dev)
+{
+	struct tilcdc_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 = tilcdc_fb_create,
+	.output_poll_changed = tilcdc_fb_output_poll_changed,
+};
+
+static int modeset_init(struct drm_device *dev)
+{
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	struct tilcdc_module *mod;
+
+	drm_mode_config_init(dev);
+
+	priv->crtc = tilcdc_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 = tilcdc_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 tilcdc_drm_private *priv = container_of(nb,
+			struct tilcdc_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);
+			tilcdc_crtc_update_clk(priv->crtc);
+		}
+	}
+
+	return 0;
+}
+#endif
+
+/*
+ * DRM operations:
+ */
+
+static int tilcdc_unload(struct drm_device *dev)
+{
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	struct tilcdc_module *mod, *cur;
+
+	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);
+
+	flush_workqueue(priv->wq);
+	destroy_workqueue(priv->wq);
+
+	dev->dev_private = NULL;
+
+	pm_runtime_disable(dev->dev);
+
+	list_for_each_entry_safe(mod, cur, &module_list, list) {
+		DBG("destroying module: %s", mod->name);
+		mod->funcs->destroy(mod);
+	}
+
+	kfree(priv);
+
+	return 0;
+}
+
+static int tilcdc_load(struct drm_device *dev, unsigned long flags)
+{
+	struct platform_device *pdev = dev->platformdev;
+	struct device_node *node = pdev->dev.of_node;
+	struct tilcdc_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;
+
+	priv->wq = alloc_ordered_workqueue("tilcdc", 0);
+
+	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
+
+	if (of_property_read_u32(node, "max-bandwidth", &priv->max_bandwidth))
+		priv->max_bandwidth = 1280 * 1024 * 60;
+
+	pm_runtime_enable(dev->dev);
+
+	/* Determine LCD IP Version */
+	pm_runtime_get_sync(dev->dev);
+	switch (tilcdc_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",
+				tilcdc_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:
+	tilcdc_unload(dev);
+	return ret;
+}
+
+static void tilcdc_preclose(struct drm_device *dev, struct drm_file *file)
+{
+	struct tilcdc_drm_private *priv = dev->dev_private;
+
+	tilcdc_crtc_cancel_page_flip(priv->crtc, file);
+}
+
+static void tilcdc_lastclose(struct drm_device *dev)
+{
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	drm_fbdev_cma_restore_mode(priv->fbdev);
+}
+
+static irqreturn_t tilcdc_irq(DRM_IRQ_ARGS)
+{
+	struct drm_device *dev = arg;
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	return tilcdc_crtc_irq(priv->crtc);
+}
+
+static void tilcdc_irq_preinstall(struct drm_device *dev)
+{
+	tilcdc_clear_irqstatus(dev, 0xffffffff);
+}
+
+static int tilcdc_irq_postinstall(struct drm_device *dev)
+{
+	struct tilcdc_drm_private *priv = dev->dev_private;
+
+	/* enable FIFO underflow irq: */
+	if (priv->rev == 1) {
+		tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_V1_UNDERFLOW_INT_ENA);
+	} else {
+		tilcdc_set(dev, LCDC_INT_ENABLE_SET_REG, LCDC_V2_UNDERFLOW_INT_ENA);
+	}
+
+	return 0;
+}
+
+static void tilcdc_irq_uninstall(struct drm_device *dev)
+{
+	struct tilcdc_drm_private *priv = dev->dev_private;
+
+	/* disable irqs that we might have enabled: */
+	if (priv->rev == 1) {
+		tilcdc_clear(dev, LCDC_RASTER_CTRL_REG,
+				LCDC_V1_UNDERFLOW_INT_ENA | LCDC_V1_PL_INT_ENA);
+		tilcdc_clear(dev, LCDC_DMA_CTRL_REG, LCDC_V1_END_OF_FRAME_INT_ENA);
+	} else {
+		tilcdc_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 tilcdc_drm_private *priv = dev->dev_private;
+	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;
+	}
+
+	if (enable)
+		tilcdc_set(dev, reg, mask);
+	else
+		tilcdc_clear(dev, reg, mask);
+}
+
+static int tilcdc_enable_vblank(struct drm_device *dev, int crtc)
+{
+	enable_vblank(dev, true);
+	return 0;
+}
+
+static void tilcdc_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_FB_BASE_ADDR_0_REG),
+		REG(1, true,  LCDC_DMA_FB_CEILING_ADDR_0_REG),
+		REG(1, true,  LCDC_DMA_FB_BASE_ADDR_1_REG),
+		REG(1, true,  LCDC_DMA_FB_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 tilcdc_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 tilcdc_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,
+					tilcdc_read(dev, registers[i].reg));
+
+	pm_runtime_put_sync(dev->dev);
+
+	return 0;
+}
+
+static int tilcdc_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 tilcdc_debugfs_list[] = {
+		{ "regs", tilcdc_regs_show, 0 },
+		{ "mm",   tilcdc_mm_show,   0 },
+		{ "fb",   drm_fb_cma_debugfs_show, 0 },
+};
+
+static int tilcdc_debugfs_init(struct drm_minor *minor)
+{
+	struct drm_device *dev = minor->dev;
+	struct tilcdc_module *mod;
+	int ret;
+
+	ret = drm_debugfs_create_files(tilcdc_debugfs_list,
+			ARRAY_SIZE(tilcdc_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 tilcdc_debugfs_list\n");
+		return ret;
+	}
+
+	return ret;
+}
+
+static void tilcdc_debugfs_cleanup(struct drm_minor *minor)
+{
+	struct tilcdc_module *mod;
+	drm_debugfs_remove_files(tilcdc_debugfs_list,
+			ARRAY_SIZE(tilcdc_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 tilcdc_driver = {
+	.driver_features    = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET,
+	.load               = tilcdc_load,
+	.unload             = tilcdc_unload,
+	.preclose           = tilcdc_preclose,
+	.lastclose          = tilcdc_lastclose,
+	.irq_handler        = tilcdc_irq,
+	.irq_preinstall     = tilcdc_irq_preinstall,
+	.irq_postinstall    = tilcdc_irq_postinstall,
+	.irq_uninstall      = tilcdc_irq_uninstall,
+	.get_vblank_counter = drm_vblank_count,
+	.enable_vblank      = tilcdc_enable_vblank,
+	.disable_vblank     = tilcdc_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       = tilcdc_debugfs_init,
+	.debugfs_cleanup    = tilcdc_debugfs_cleanup,
+#endif
+	.fops               = &fops,
+	.name               = "tilcdc",
+	.desc               = "TI LCD Controller DRM",
+	.date               = "20121205",
+	.major              = 1,
+	.minor              = 0,
+};
+
+/*
+ * Power management:
+ */
+
+#if CONFIG_PM_SLEEP
+static int tilcdc_pm_suspend(struct device *dev)
+{
+	struct drm_device *ddev = dev_get_drvdata(dev);
+	struct tilcdc_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++] = tilcdc_read(ddev, registers[i].reg);
+
+	return 0;
+}
+
+static int tilcdc_pm_resume(struct device *dev)
+{
+	struct drm_device *ddev = dev_get_drvdata(dev);
+	struct tilcdc_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))
+			tilcdc_write(ddev, registers[i].reg, priv->saved_register[n++]);
+
+	drm_kms_helper_poll_enable(ddev);
+
+	return 0;
+}
+#endif
+
+static const struct dev_pm_ops tilcdc_pm_ops = {
+	SET_SYSTEM_SLEEP_PM_OPS(tilcdc_pm_suspend, tilcdc_pm_resume)
+};
+
+/*
+ * Platform driver:
+ */
+
+static int tilcdc_pdev_probe(struct platform_device *pdev)
+{
+	/* bail out early if no DT data: */
+	if (!pdev->dev.of_node) {
+		dev_err(&pdev->dev, "device-tree data is missing\n");
+		return -ENXIO;
+	}
+
+	return drm_platform_init(&tilcdc_driver, pdev);
+}
+
+static int tilcdc_pdev_remove(struct platform_device *pdev)
+{
+	drm_platform_exit(&tilcdc_driver, pdev);
+
+	return 0;
+}
+
+static struct of_device_id tilcdc_of_match[] = {
+		{ .compatible = "ti,am33xx-tilcdc", },
+		{ },
+};
+MODULE_DEVICE_TABLE(of, tilcdc_of_match);
+
+static struct platform_driver tilcdc_platform_driver = {
+	.probe      = tilcdc_pdev_probe,
+	.remove     = tilcdc_pdev_remove,
+	.driver     = {
+		.owner  = THIS_MODULE,
+		.name   = "tilcdc",
+		.pm     = &tilcdc_pm_ops,
+		.of_match_table = tilcdc_of_match,
+	},
+};
+
+static int __init tilcdc_drm_init(void)
+{
+	DBG("init");
+	tilcdc_tfp410_init();
+	return platform_driver_register(&tilcdc_platform_driver);
+}
+
+static void __exit tilcdc_drm_fini(void)
+{
+	DBG("fini");
+	tilcdc_tfp410_fini();
+	platform_driver_unregister(&tilcdc_platform_driver);
+}
+
+module_init(tilcdc_drm_init);
+module_exit(tilcdc_drm_fini);
+
+MODULE_AUTHOR("Rob Clark <robdclark at gmail.com");
+MODULE_DESCRIPTION("TI LCD Controller DRM Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.h b/drivers/gpu/drm/tilcdc/tilcdc_drv.h
new file mode 100644
index 0000000..76321cd
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.h
@@ -0,0 +1,159 @@
+/*
+ * 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 __TILCDC_DRV_H__
+#define __TILCDC_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 tilcdc_drm_private {
+	void __iomem *mmio;
+
+	struct clk *disp_clk;    /* display dpll */
+	struct clk *clk;         /* functional clock */
+	int rev;                 /* IP revision */
+
+	/* don't attempt resolutions w/ higher W * H * Hz: */
+	uint32_t max_bandwidth;
+
+	/* register contents saved across suspend/resume: */
+	u32 saved_register[12];
+
+#ifdef CONFIG_CPU_FREQ
+	struct notifier_block freq_transition;
+	unsigned int lcd_fck_rate;
+#endif
+
+	struct workqueue_struct *wq;
+
+	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 tilcdc_register_module().
+ */
+struct tilcdc_module;
+
+struct tilcdc_module_ops {
+	/* create appropriate encoders/connectors: */
+	int (*modeset_init)(struct tilcdc_module *mod, struct drm_device *dev);
+	void (*destroy)(struct tilcdc_module *mod);
+#ifdef CONFIG_DEBUG_FS
+	/* create debugfs nodes (can be NULL): */
+	int (*debugfs_init)(struct tilcdc_module *mod, struct drm_minor *minor);
+	/* cleanup debugfs nodes (can be NULL): */
+	void (*debugfs_cleanup)(struct tilcdc_module *mod, struct drm_minor *minor);
+#endif
+};
+
+struct tilcdc_module {
+	const char *name;
+	struct list_head list;
+	const struct tilcdc_module_ops *funcs;
+};
+
+void tilcdc_module_init(struct tilcdc_module *mod, const char *name,
+		const struct tilcdc_module_ops *funcs);
+void tilcdc_module_cleanup(struct tilcdc_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
+ * tilcdc_crtc_set_panel_info() to set this during modeset.
+ */
+struct tilcdc_panel_info {
+
+	uint32_t max_bpp;
+	uint32_t min_bpp;
+
+	/* AC Bias Pin Frequency */
+	uint32_t ac_bias;
+
+	/* AC Bias Pin Transitions per Interrupt */
+	uint32_t ac_bias_intrpt;
+
+	/* DMA burst size */
+	uint32_t dma_burst_sz;
+
+	/* Bits per pixel */
+	uint32_t bpp;
+
+	/* FIFO DMA Request Delay */
+	uint32_t fdd;
+
+	/* TFT Alternative Signal Mapping (Only for active) */
+	bool tft_alt_mode;
+
+	/* 12 Bit Per Pixel (5-6-5) Mode (Only for passive) */
+	bool stn_565_mode;
+
+	/* Mono 8-bit Mode: 1=D0-D7 or 0=D0-D3 */
+	bool mono_8bit_mode;
+
+	/* Invert pixel clock */
+	bool invert_pxl_clk;
+
+	/* Horizontal and Vertical Sync Edge: 0=rising 1=falling */
+	uint32_t sync_edge;
+
+	/* Horizontal and Vertical Sync: Control: 0=ignore */
+	uint32_t sync_ctrl;
+
+	/* Raster Data Order Select: 1=Most-to-least 0=Least-to-most */
+	uint32_t raster_order;
+
+	/* DMA FIFO threshold */
+	uint32_t fifo_th;
+};
+
+#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
+
+struct drm_crtc *tilcdc_crtc_create(struct drm_device *dev);
+void tilcdc_crtc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file);
+irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc);
+void tilcdc_crtc_update_clk(struct drm_crtc *crtc);
+void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc,
+		const struct tilcdc_panel_info *info);
+int tilcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode);
+int tilcdc_crtc_max_width(struct drm_crtc *crtc);
+
+#endif /* __TILCDC_DRV_H__ */
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_regs.h b/drivers/gpu/drm/tilcdc/tilcdc_regs.h
new file mode 100644
index 0000000..17fd1b4
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_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 __TILCDC_REGS_H__
+#define __TILCDC_REGS_H__
+
+/* LCDC register definitions, based on da8xx-fb */
+
+#include <linux/bitops.h>
+
+#include "tilcdc_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_HSYNC                        BIT(21)
+#define LCDC_INVERT_VSYNC                        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_FB_BASE_ADDR_0_REG              0x44
+#define LCDC_DMA_FB_CEILING_ADDR_0_REG           0x48
+#define LCDC_DMA_FB_BASE_ADDR_1_REG              0x4c
+#define LCDC_DMA_FB_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 tilcdc_write(struct drm_device *dev, u32 reg, u32 data)
+{
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	iowrite32(data, priv->mmio + reg);
+}
+
+static inline u32 tilcdc_read(struct drm_device *dev, u32 reg)
+{
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	return ioread32(priv->mmio + reg);
+}
+
+static inline void tilcdc_set(struct drm_device *dev, u32 reg, u32 mask)
+{
+	tilcdc_write(dev, reg, tilcdc_read(dev, reg) | mask);
+}
+
+static inline void tilcdc_clear(struct drm_device *dev, u32 reg, u32 mask)
+{
+	tilcdc_write(dev, reg, tilcdc_read(dev, reg) & ~mask);
+}
+
+/* the register to read/clear irqstatus differs between v1 and v2 of the IP */
+static inline u32 tilcdc_irqstatus_reg(struct drm_device *dev)
+{
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	return (priv->rev == 2) ? LCDC_MASKED_STAT_REG : LCDC_STAT_REG;
+}
+
+static inline u32 tilcdc_read_irqstatus(struct drm_device *dev)
+{
+	return tilcdc_read(dev, tilcdc_irqstatus_reg(dev));
+}
+
+static inline void tilcdc_clear_irqstatus(struct drm_device *dev, u32 mask)
+{
+	tilcdc_write(dev, tilcdc_irqstatus_reg(dev), mask);
+}
+
+#endif /* __TILCDC_REGS_H__ */
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_tfp410.c b/drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
new file mode 100644
index 0000000..eaae4dd
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
@@ -0,0 +1,423 @@
+/*
+ * 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 "tilcdc_drv.h"
+
+struct tfp410_module {
+	struct tilcdc_module base;
+	struct i2c_adapter *i2c;
+	int gpio;
+};
+#define to_tfp410_module(x) container_of(x, struct tfp410_module, base)
+
+
+static const struct tilcdc_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,
+		.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);
+	tilcdc_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 tilcdc_drm_private *priv = connector->dev->dev_private;
+	/* our only constraints are what the crtc can generate: */
+	return tilcdc_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 tilcdc_module *mod, struct drm_device *dev)
+{
+	struct tfp410_module *tfp410_mod = to_tfp410_module(mod);
+	struct tilcdc_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 tilcdc_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);
+
+	tilcdc_module_cleanup(mod);
+	kfree(tfp410_mod);
+}
+
+static const struct tilcdc_module_ops tfp410_module_ops = {
+		.modeset_init = tfp410_modeset_init,
+		.destroy = tfp410_destroy,
+};
+
+/*
+ * Device:
+ */
+
+static struct of_device_id tfp410_of_match[];
+
+static int 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 tilcdc_module *mod;
+	struct pinctrl *pinctrl;
+	uint32_t i2c_phandle;
+	int ret = -EINVAL;
+
+	/* bail out early if no DT data: */
+	if (!node) {
+		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;
+
+	tilcdc_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 tfp410_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static struct of_device_id tfp410_of_match[] = {
+		{ .compatible = "tilcdc,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 tilcdc_tfp410_init(void)
+{
+	return platform_driver_register(&tfp410_driver);
+}
+
+void __exit tilcdc_tfp410_fini(void)
+{
+	platform_driver_unregister(&tfp410_driver);
+}
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_tfp410.h b/drivers/gpu/drm/tilcdc/tilcdc_tfp410.h
new file mode 100644
index 0000000..5b800f1
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_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 __TILCDC_TFP410_H__
+#define __TILCDC_TFP410_H__
+
+/* sub-module for tfp410 dvi adaptor */
+
+int tilcdc_tfp410_init(void);
+void tilcdc_tfp410_fini(void);
+
+#endif /* __TILCDC_TFP410_H__ */
-- 
1.8.1

^ permalink raw reply related	[flat|nested] 66+ messages in thread

* [PATCH 2/4] drm/i2c: nxp-tda998x (v2)
  2013-01-22 22:36 ` Rob Clark
@ 2013-01-22 22:36   ` Rob Clark
  -1 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-22 22:36 UTC (permalink / raw)
  To: dri-devel; +Cc: patches, linux-omap, linux-arm-kernel, Rob Clark

Driver for the NXP TDA998X i2c hdmi encoder slave.

v1: original
v2: fix npix/nline programming

Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 drivers/gpu/drm/i2c/Makefile      |   3 +
 drivers/gpu/drm/i2c/tda998x_drv.c | 908 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 911 insertions(+)
 create mode 100644 drivers/gpu/drm/i2c/tda998x_drv.c

diff --git a/drivers/gpu/drm/i2c/Makefile b/drivers/gpu/drm/i2c/Makefile
index 9286256..43aa33b 100644
--- a/drivers/gpu/drm/i2c/Makefile
+++ b/drivers/gpu/drm/i2c/Makefile
@@ -5,3 +5,6 @@ obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o
 
 sil164-y := sil164_drv.o
 obj-$(CONFIG_DRM_I2C_SIL164) += sil164.o
+
+tda998x-y := tda998x_drv.o
+obj-$(CONFIG_DRM_I2C_NXP_TDA998X) += tda998x.o
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
new file mode 100644
index 0000000..02054e8
--- /dev/null
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -0,0 +1,908 @@
+/*
+ * 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/module.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder_slave.h>
+#include <drm/drm_edid.h>
+
+
+#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
+
+struct tda998x_priv {
+	struct i2c_client *cec;
+	uint16_t rev;
+	uint8_t current_page;
+	int dpms;
+};
+
+#define to_tda998x_priv(x)  ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv)
+
+/* The TDA9988 series of devices use a paged register scheme.. to simplify
+ * things we encode the page # in upper bits of the register #.  To read/
+ * write a given register, we need to make sure CURPAGE register is set
+ * appropriately.  Which implies reads/writes are not atomic.  Fun!
+ */
+
+#define REG(page, addr) (((page) << 8) | (addr))
+#define REG2ADDR(reg)   ((reg) & 0xff)
+#define REG2PAGE(reg)   (((reg) >> 8) & 0xff)
+
+#define REG_CURPAGE               0xff                /* write */
+
+
+/* Page 00h: General Control */
+#define REG_VERSION_LSB           REG(0x00, 0x00)     /* read */
+#define REG_MAIN_CNTRL0           REG(0x00, 0x01)     /* read/write */
+# define MAIN_CNTRL0_SR           (1 << 0)
+# define MAIN_CNTRL0_DECS         (1 << 1)
+# define MAIN_CNTRL0_DEHS         (1 << 2)
+# define MAIN_CNTRL0_CECS         (1 << 3)
+# define MAIN_CNTRL0_CEHS         (1 << 4)
+# define MAIN_CNTRL0_SCALER       (1 << 7)
+#define REG_VERSION_MSB           REG(0x00, 0x02)     /* read */
+#define REG_SOFTRESET             REG(0x00, 0x0a)     /* write */
+# define SOFTRESET_AUDIO          (1 << 0)
+# define SOFTRESET_I2C_MASTER     (1 << 1)
+#define REG_DDC_DISABLE           REG(0x00, 0x0b)     /* read/write */
+#define REG_CCLK_ON               REG(0x00, 0x0c)     /* read/write */
+#define REG_I2C_MASTER            REG(0x00, 0x0d)     /* read/write */
+# define I2C_MASTER_DIS_MM        (1 << 0)
+# define I2C_MASTER_DIS_FILT      (1 << 1)
+# define I2C_MASTER_APP_STRT_LAT  (1 << 2)
+#define REG_INT_FLAGS_0           REG(0x00, 0x0f)     /* read/write */
+#define REG_INT_FLAGS_1           REG(0x00, 0x10)     /* read/write */
+#define REG_INT_FLAGS_2           REG(0x00, 0x11)     /* read/write */
+# define INT_FLAGS_2_EDID_BLK_RD  (1 << 1)
+#define REG_ENA_VP_0              REG(0x00, 0x18)     /* read/write */
+#define REG_ENA_VP_1              REG(0x00, 0x19)     /* read/write */
+#define REG_ENA_VP_2              REG(0x00, 0x1a)     /* read/write */
+#define REG_ENA_AP                REG(0x00, 0x1e)     /* read/write */
+#define REG_VIP_CNTRL_0           REG(0x00, 0x20)     /* write */
+# define VIP_CNTRL_0_MIRR_A       (1 << 7)
+# define VIP_CNTRL_0_SWAP_A(x)    (((x) & 7) << 4)
+# define VIP_CNTRL_0_MIRR_B       (1 << 3)
+# define VIP_CNTRL_0_SWAP_B(x)    (((x) & 7) << 0)
+#define REG_VIP_CNTRL_1           REG(0x00, 0x21)     /* write */
+# define VIP_CNTRL_1_MIRR_C       (1 << 7)
+# define VIP_CNTRL_1_SWAP_C(x)    (((x) & 7) << 4)
+# define VIP_CNTRL_1_MIRR_D       (1 << 3)
+# define VIP_CNTRL_1_SWAP_D(x)    (((x) & 7) << 0)
+#define REG_VIP_CNTRL_2           REG(0x00, 0x22)     /* write */
+# define VIP_CNTRL_2_MIRR_E       (1 << 7)
+# define VIP_CNTRL_2_SWAP_E(x)    (((x) & 7) << 4)
+# define VIP_CNTRL_2_MIRR_F       (1 << 3)
+# define VIP_CNTRL_2_SWAP_F(x)    (((x) & 7) << 0)
+#define REG_VIP_CNTRL_3           REG(0x00, 0x23)     /* write */
+# define VIP_CNTRL_3_X_TGL        (1 << 0)
+# define VIP_CNTRL_3_H_TGL        (1 << 1)
+# define VIP_CNTRL_3_V_TGL        (1 << 2)
+# define VIP_CNTRL_3_EMB          (1 << 3)
+# define VIP_CNTRL_3_SYNC_DE      (1 << 4)
+# define VIP_CNTRL_3_SYNC_HS      (1 << 5)
+# define VIP_CNTRL_3_DE_INT       (1 << 6)
+# define VIP_CNTRL_3_EDGE         (1 << 7)
+#define REG_VIP_CNTRL_4           REG(0x00, 0x24)     /* write */
+# define VIP_CNTRL_4_BLC(x)       (((x) & 3) << 0)
+# define VIP_CNTRL_4_BLANKIT(x)   (((x) & 3) << 2)
+# define VIP_CNTRL_4_CCIR656      (1 << 4)
+# define VIP_CNTRL_4_656_ALT      (1 << 5)
+# define VIP_CNTRL_4_TST_656      (1 << 6)
+# define VIP_CNTRL_4_TST_PAT      (1 << 7)
+#define REG_VIP_CNTRL_5           REG(0x00, 0x25)     /* write */
+# define VIP_CNTRL_5_CKCASE       (1 << 0)
+# define VIP_CNTRL_5_SP_CNT(x)    (((x) & 3) << 1)
+#define REG_MAT_CONTRL            REG(0x00, 0x80)     /* write */
+# define MAT_CONTRL_MAT_SC(x)     (((x) & 3) << 0)
+# define MAT_CONTRL_MAT_BP        (1 << 2)
+#define REG_VIDFORMAT             REG(0x00, 0xa0)     /* write */
+#define REG_REFPIX_MSB            REG(0x00, 0xa1)     /* write */
+#define REG_REFPIX_LSB            REG(0x00, 0xa2)     /* write */
+#define REG_REFLINE_MSB           REG(0x00, 0xa3)     /* write */
+#define REG_REFLINE_LSB           REG(0x00, 0xa4)     /* write */
+#define REG_NPIX_MSB              REG(0x00, 0xa5)     /* write */
+#define REG_NPIX_LSB              REG(0x00, 0xa6)     /* write */
+#define REG_NLINE_MSB             REG(0x00, 0xa7)     /* write */
+#define REG_NLINE_LSB             REG(0x00, 0xa8)     /* write */
+#define REG_VS_LINE_STRT_1_MSB    REG(0x00, 0xa9)     /* write */
+#define REG_VS_LINE_STRT_1_LSB    REG(0x00, 0xaa)     /* write */
+#define REG_VS_PIX_STRT_1_MSB     REG(0x00, 0xab)     /* write */
+#define REG_VS_PIX_STRT_1_LSB     REG(0x00, 0xac)     /* write */
+#define REG_VS_LINE_END_1_MSB     REG(0x00, 0xad)     /* write */
+#define REG_VS_LINE_END_1_LSB     REG(0x00, 0xae)     /* write */
+#define REG_VS_PIX_END_1_MSB      REG(0x00, 0xaf)     /* write */
+#define REG_VS_PIX_END_1_LSB      REG(0x00, 0xb0)     /* write */
+#define REG_VS_PIX_STRT_2_MSB     REG(0x00, 0xb3)     /* write */
+#define REG_VS_PIX_STRT_2_LSB     REG(0x00, 0xb4)     /* write */
+#define REG_VS_PIX_END_2_MSB      REG(0x00, 0xb7)     /* write */
+#define REG_VS_PIX_END_2_LSB      REG(0x00, 0xb8)     /* write */
+#define REG_HS_PIX_START_MSB      REG(0x00, 0xb9)     /* write */
+#define REG_HS_PIX_START_LSB      REG(0x00, 0xba)     /* write */
+#define REG_HS_PIX_STOP_MSB       REG(0x00, 0xbb)     /* write */
+#define REG_HS_PIX_STOP_LSB       REG(0x00, 0xbc)     /* write */
+#define REG_VWIN_START_1_MSB      REG(0x00, 0xbd)     /* write */
+#define REG_VWIN_START_1_LSB      REG(0x00, 0xbe)     /* write */
+#define REG_VWIN_END_1_MSB        REG(0x00, 0xbf)     /* write */
+#define REG_VWIN_END_1_LSB        REG(0x00, 0xc0)     /* write */
+#define REG_DE_START_MSB          REG(0x00, 0xc5)     /* write */
+#define REG_DE_START_LSB          REG(0x00, 0xc6)     /* write */
+#define REG_DE_STOP_MSB           REG(0x00, 0xc7)     /* write */
+#define REG_DE_STOP_LSB           REG(0x00, 0xc8)     /* write */
+#define REG_TBG_CNTRL_0           REG(0x00, 0xca)     /* write */
+# define TBG_CNTRL_0_FRAME_DIS    (1 << 5)
+# define TBG_CNTRL_0_SYNC_MTHD    (1 << 6)
+# define TBG_CNTRL_0_SYNC_ONCE    (1 << 7)
+#define REG_TBG_CNTRL_1           REG(0x00, 0xcb)     /* write */
+# define TBG_CNTRL_1_VH_TGL_0     (1 << 0)
+# define TBG_CNTRL_1_VH_TGL_1     (1 << 1)
+# define TBG_CNTRL_1_VH_TGL_2     (1 << 2)
+# define TBG_CNTRL_1_VHX_EXT_DE   (1 << 3)
+# define TBG_CNTRL_1_VHX_EXT_HS   (1 << 4)
+# define TBG_CNTRL_1_VHX_EXT_VS   (1 << 5)
+# define TBG_CNTRL_1_DWIN_DIS     (1 << 6)
+#define REG_ENABLE_SPACE          REG(0x00, 0xd6)     /* write */
+#define REG_HVF_CNTRL_0           REG(0x00, 0xe4)     /* write */
+# define HVF_CNTRL_0_SM           (1 << 7)
+# define HVF_CNTRL_0_RWB          (1 << 6)
+# define HVF_CNTRL_0_PREFIL(x)    (((x) & 3) << 2)
+# define HVF_CNTRL_0_INTPOL(x)    (((x) & 3) << 0)
+#define REG_HVF_CNTRL_1           REG(0x00, 0xe5)     /* write */
+# define HVF_CNTRL_1_FOR          (1 << 0)
+# define HVF_CNTRL_1_YUVBLK       (1 << 1)
+# define HVF_CNTRL_1_VQR(x)       (((x) & 3) << 2)
+# define HVF_CNTRL_1_PAD(x)       (((x) & 3) << 4)
+# define HVF_CNTRL_1_SEMI_PLANAR  (1 << 6)
+#define REG_RPT_CNTRL             REG(0x00, 0xf0)     /* write */
+
+
+/* Page 02h: PLL settings */
+#define REG_PLL_SERIAL_1          REG(0x02, 0x00)     /* read/write */
+# define PLL_SERIAL_1_SRL_FDN     (1 << 0)
+# define PLL_SERIAL_1_SRL_IZ(x)   (((x) & 3) << 1)
+# define PLL_SERIAL_1_SRL_MAN_IZ  (1 << 6)
+#define REG_PLL_SERIAL_2          REG(0x02, 0x01)     /* read/write */
+# define PLL_SERIAL_2_SRL_NOSC(x) (((x) & 3) << 0)
+# define PLL_SERIAL_2_SRL_PR(x)   (((x) & 0xf) << 4)
+#define REG_PLL_SERIAL_3          REG(0x02, 0x02)     /* read/write */
+# define PLL_SERIAL_3_SRL_CCIR    (1 << 0)
+# define PLL_SERIAL_3_SRL_DE      (1 << 2)
+# define PLL_SERIAL_3_SRL_PXIN_SEL (1 << 4)
+#define REG_SERIALIZER            REG(0x02, 0x03)     /* read/write */
+#define REG_BUFFER_OUT            REG(0x02, 0x04)     /* read/write */
+#define REG_PLL_SCG1              REG(0x02, 0x05)     /* read/write */
+#define REG_PLL_SCG2              REG(0x02, 0x06)     /* read/write */
+#define REG_PLL_SCGN1             REG(0x02, 0x07)     /* read/write */
+#define REG_PLL_SCGN2             REG(0x02, 0x08)     /* read/write */
+#define REG_PLL_SCGR1             REG(0x02, 0x09)     /* read/write */
+#define REG_PLL_SCGR2             REG(0x02, 0x0a)     /* read/write */
+#define REG_AUDIO_DIV             REG(0x02, 0x0e)     /* read/write */
+#define REG_SEL_CLK               REG(0x02, 0x11)     /* read/write */
+# define SEL_CLK_SEL_CLK1         (1 << 0)
+# define SEL_CLK_SEL_VRF_CLK(x)   (((x) & 3) << 1)
+# define SEL_CLK_ENA_SC_CLK       (1 << 3)
+#define REG_ANA_GENERAL           REG(0x02, 0x12)     /* read/write */
+
+
+/* Page 09h: EDID Control */
+#define REG_EDID_DATA_0           REG(0x09, 0x00)     /* read */
+/* next 127 successive registers are the EDID block */
+#define REG_EDID_CTRL             REG(0x09, 0xfa)     /* read/write */
+#define REG_DDC_ADDR              REG(0x09, 0xfb)     /* read/write */
+#define REG_DDC_OFFS              REG(0x09, 0xfc)     /* read/write */
+#define REG_DDC_SEGM_ADDR         REG(0x09, 0xfd)     /* read/write */
+#define REG_DDC_SEGM              REG(0x09, 0xfe)     /* read/write */
+
+
+/* Page 10h: information frames and packets */
+
+
+/* Page 11h: audio settings and content info packets */
+#define REG_AIP_CNTRL_0           REG(0x11, 0x00)     /* read/write */
+# define AIP_CNTRL_0_RST_FIFO     (1 << 0)
+# define AIP_CNTRL_0_SWAP         (1 << 1)
+# define AIP_CNTRL_0_LAYOUT       (1 << 2)
+# define AIP_CNTRL_0_ACR_MAN      (1 << 5)
+# define AIP_CNTRL_0_RST_CTS      (1 << 6)
+#define REG_ENC_CNTRL             REG(0x11, 0x0d)     /* read/write */
+# define ENC_CNTRL_RST_ENC        (1 << 0)
+# define ENC_CNTRL_RST_SEL        (1 << 1)
+# define ENC_CNTRL_CTL_CODE(x)    (((x) & 3) << 2)
+
+
+/* Page 12h: HDCP and OTP */
+#define REG_TX3                   REG(0x12, 0x9a)     /* read/write */
+#define REG_TX33                  REG(0x12, 0xb8)     /* read/write */
+# define TX33_HDMI                (1 << 1)
+
+
+/* Page 13h: Gamut related metadata packets */
+
+
+
+/* CEC registers: (not paged)
+ */
+#define REG_CEC_FRO_IM_CLK_CTRL   0xfb                /* read/write */
+# define CEC_FRO_IM_CLK_CTRL_GHOST_DIS (1 << 7)
+# define CEC_FRO_IM_CLK_CTRL_ENA_OTP   (1 << 6)
+# define CEC_FRO_IM_CLK_CTRL_IMCLK_SEL (1 << 1)
+# define CEC_FRO_IM_CLK_CTRL_FRO_DIV   (1 << 0)
+#define REG_CEC_RXSHPDLEV         0xfe                /* read */
+# define CEC_RXSHPDLEV_RXSENS     (1 << 0)
+# define CEC_RXSHPDLEV_HPD        (1 << 1)
+
+#define REG_CEC_ENAMODS           0xff                /* read/write */
+# define CEC_ENAMODS_DIS_FRO      (1 << 6)
+# define CEC_ENAMODS_DIS_CCLK     (1 << 5)
+# define CEC_ENAMODS_EN_RXSENS    (1 << 2)
+# define CEC_ENAMODS_EN_HDMI      (1 << 1)
+# define CEC_ENAMODS_EN_CEC       (1 << 0)
+
+
+/* Device versions: */
+#define TDA9989N2                 0x0101
+#define TDA19989                  0x0201
+#define TDA19989N2                0x0202
+#define TDA19988                  0x0301
+
+static void
+cec_write(struct drm_encoder *encoder, uint16_t addr, uint8_t val)
+{
+	struct i2c_client *client = to_tda998x_priv(encoder)->cec;
+	uint8_t buf[] = {addr, val};
+	int ret;
+
+	ret = i2c_master_send(client, buf, ARRAY_SIZE(buf));
+	if (ret < 0)
+		dev_err(&client->dev, "Error %d writing to cec:0x%x\n", ret, addr);
+}
+
+static uint8_t
+cec_read(struct drm_encoder *encoder, uint8_t addr)
+{
+	struct i2c_client *client = to_tda998x_priv(encoder)->cec;
+	uint8_t val;
+	int ret;
+
+	ret = i2c_master_send(client, &addr, sizeof(addr));
+	if (ret < 0)
+		goto fail;
+
+	ret = i2c_master_recv(client, &val, sizeof(val));
+	if (ret < 0)
+		goto fail;
+
+	return val;
+
+fail:
+	dev_err(&client->dev, "Error %d reading from cec:0x%x\n", ret, addr);
+	return 0;
+}
+
+static void
+set_page(struct drm_encoder *encoder, uint16_t reg)
+{
+	struct tda998x_priv *priv = to_tda998x_priv(encoder);
+
+	if (REG2PAGE(reg) != priv->current_page) {
+		struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+		uint8_t buf[] = {
+				REG_CURPAGE, REG2PAGE(reg)
+		};
+		int ret = i2c_master_send(client, buf, sizeof(buf));
+		if (ret < 0)
+			dev_err(&client->dev, "Error %d writing to REG_CURPAGE\n", ret);
+
+		priv->current_page = REG2PAGE(reg);
+	}
+}
+
+static int
+reg_read_range(struct drm_encoder *encoder, uint16_t reg, char *buf, int cnt)
+{
+	struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+	uint8_t addr = REG2ADDR(reg);
+	int ret;
+
+	set_page(encoder, reg);
+
+	ret = i2c_master_send(client, &addr, sizeof(addr));
+	if (ret < 0)
+		goto fail;
+
+	ret = i2c_master_recv(client, buf, cnt);
+	if (ret < 0)
+		goto fail;
+
+	return ret;
+
+fail:
+	dev_err(&client->dev, "Error %d reading from 0x%x\n", ret, reg);
+	return ret;
+}
+
+static uint8_t
+reg_read(struct drm_encoder *encoder, uint16_t reg)
+{
+	uint8_t val = 0;
+	reg_read_range(encoder, reg, &val, sizeof(val));
+	return val;
+}
+
+static void
+reg_write(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
+{
+	struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+	uint8_t buf[] = {REG2ADDR(reg), val};
+	int ret;
+
+	set_page(encoder, reg);
+
+	ret = i2c_master_send(client, buf, ARRAY_SIZE(buf));
+	if (ret < 0)
+		dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg);
+}
+
+static void
+reg_write16(struct drm_encoder *encoder, uint16_t reg, uint16_t val)
+{
+	struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+	uint8_t buf[] = {REG2ADDR(reg), val >> 8, val};
+	int ret;
+
+	set_page(encoder, reg);
+
+	ret = i2c_master_send(client, buf, ARRAY_SIZE(buf));
+	if (ret < 0)
+		dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg);
+}
+
+static void
+reg_set(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
+{
+	reg_write(encoder, reg, reg_read(encoder, reg) | val);
+}
+
+static void
+reg_clear(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
+{
+	reg_write(encoder, reg, reg_read(encoder, reg) & ~val);
+}
+
+static void
+tda998x_reset(struct drm_encoder *encoder)
+{
+	/* reset audio and i2c master: */
+	reg_set(encoder, REG_SOFTRESET, SOFTRESET_AUDIO | SOFTRESET_I2C_MASTER);
+	msleep(50);
+	reg_clear(encoder, REG_SOFTRESET, SOFTRESET_AUDIO | SOFTRESET_I2C_MASTER);
+	msleep(50);
+
+	/* reset transmitter: */
+	reg_set(encoder, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR);
+	reg_clear(encoder, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR);
+
+	/* PLL registers common configuration */
+	reg_write(encoder, REG_PLL_SERIAL_1, 0x00);
+	reg_write(encoder, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(1));
+	reg_write(encoder, REG_PLL_SERIAL_3, 0x00);
+	reg_write(encoder, REG_SERIALIZER,   0x00);
+	reg_write(encoder, REG_BUFFER_OUT,   0x00);
+	reg_write(encoder, REG_PLL_SCG1,     0x00);
+	reg_write(encoder, REG_AUDIO_DIV,    0x03);
+	reg_write(encoder, REG_SEL_CLK,      SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK);
+	reg_write(encoder, REG_PLL_SCGN1,    0xfa);
+	reg_write(encoder, REG_PLL_SCGN2,    0x00);
+	reg_write(encoder, REG_PLL_SCGR1,    0x5b);
+	reg_write(encoder, REG_PLL_SCGR2,    0x00);
+	reg_write(encoder, REG_PLL_SCG2,     0x10);
+}
+
+/* DRM encoder functions */
+
+static void
+tda998x_encoder_set_config(struct drm_encoder *encoder, void *params)
+{
+}
+
+static void
+tda998x_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+	struct tda998x_priv *priv = to_tda998x_priv(encoder);
+
+	/* we only care about on or off: */
+	if (mode != DRM_MODE_DPMS_ON)
+		mode = DRM_MODE_DPMS_OFF;
+
+	if (mode == priv->dpms)
+		return;
+
+	switch (mode) {
+	case DRM_MODE_DPMS_ON:
+		/* enable audio and video ports */
+		reg_write(encoder, REG_ENA_AP, 0xff);
+		reg_write(encoder, REG_ENA_VP_0, 0xff);
+		reg_write(encoder, REG_ENA_VP_1, 0xff);
+		reg_write(encoder, REG_ENA_VP_2, 0xff);
+		/* set muxing after enabling ports: */
+		reg_write(encoder, REG_VIP_CNTRL_0,
+				VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3));
+		reg_write(encoder, REG_VIP_CNTRL_1,
+				VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1));
+		reg_write(encoder, REG_VIP_CNTRL_2,
+				VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5));
+		break;
+	case DRM_MODE_DPMS_OFF:
+		/* disable audio and video ports */
+		reg_write(encoder, REG_ENA_AP, 0x00);
+		reg_write(encoder, REG_ENA_VP_0, 0x00);
+		reg_write(encoder, REG_ENA_VP_1, 0x00);
+		reg_write(encoder, REG_ENA_VP_2, 0x00);
+		break;
+	}
+
+	priv->dpms = mode;
+}
+
+static void
+tda998x_encoder_save(struct drm_encoder *encoder)
+{
+	DBG("");
+}
+
+static void
+tda998x_encoder_restore(struct drm_encoder *encoder)
+{
+	DBG("");
+}
+
+static bool
+tda998x_encoder_mode_fixup(struct drm_encoder *encoder,
+			  const struct drm_display_mode *mode,
+			  struct drm_display_mode *adjusted_mode)
+{
+	return true;
+}
+
+static int
+tda998x_encoder_mode_valid(struct drm_encoder *encoder,
+			  struct drm_display_mode *mode)
+{
+	return MODE_OK;
+}
+
+static void
+tda998x_encoder_mode_set(struct drm_encoder *encoder,
+			struct drm_display_mode *mode,
+			struct drm_display_mode *adjusted_mode)
+{
+	struct tda998x_priv *priv = to_tda998x_priv(encoder);
+	uint16_t hs_start, hs_end, line_start, line_end;
+	uint16_t vwin_start, vwin_end, de_start, de_end;
+	uint16_t ref_pix, ref_line, pix_start2;
+	uint8_t reg, div, rep;
+
+	hs_start   = mode->hsync_start - mode->hdisplay;
+	hs_end     = mode->hsync_end - mode->hdisplay;
+	line_start = 1;
+	line_end   = 1 + mode->vsync_end - mode->vsync_start;
+	vwin_start = mode->vtotal - mode->vsync_start;
+	vwin_end   = vwin_start + mode->vdisplay;
+	de_start   = mode->htotal - mode->hdisplay;
+	de_end     = mode->htotal;
+
+	pix_start2 = 0;
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+		pix_start2 = (mode->htotal / 2) + hs_start;
+
+	/* TODO how is this value calculated?  It is 2 for all common
+	 * formats in the tables in out of tree nxp driver (assuming
+	 * I've properly deciphered their byzantine table system)
+	 */
+	ref_line = 2;
+
+	/* this might changes for other color formats from the CRTC: */
+	ref_pix = 3 + hs_start;
+
+	div = 148500 / mode->clock;
+
+	DBG("clock=%d, div=%u", mode->clock, div);
+	DBG("hs_start=%u, hs_end=%u, line_start=%u, line_end=%u",
+			hs_start, hs_end, line_start, line_end);
+	DBG("vwin_start=%u, vwin_end=%u, de_start=%u, de_end=%u",
+			vwin_start, vwin_end, de_start, de_end);
+	DBG("ref_line=%u, ref_pix=%u, pix_start2=%u",
+			ref_line, ref_pix, pix_start2);
+
+	/* mute the audio FIFO: */
+	reg_set(encoder, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO);
+
+	/* set HDMI HDCP mode off: */
+	reg_set(encoder, REG_TBG_CNTRL_1, TBG_CNTRL_1_DWIN_DIS);
+	reg_clear(encoder, REG_TX33, TX33_HDMI);
+
+	reg_write(encoder, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(0));
+	/* no pre-filter or interpolator: */
+	reg_write(encoder, REG_HVF_CNTRL_0, HVF_CNTRL_0_PREFIL(0) |
+			HVF_CNTRL_0_INTPOL(0));
+	reg_write(encoder, REG_VIP_CNTRL_5, VIP_CNTRL_5_SP_CNT(0));
+	reg_write(encoder, REG_VIP_CNTRL_4, VIP_CNTRL_4_BLANKIT(0) |
+			VIP_CNTRL_4_BLC(0));
+	reg_clear(encoder, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_CCIR);
+
+	reg_clear(encoder, REG_PLL_SERIAL_1, PLL_SERIAL_1_SRL_MAN_IZ);
+	reg_clear(encoder, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_DE);
+	reg_write(encoder, REG_SERIALIZER, 0);
+	reg_write(encoder, REG_HVF_CNTRL_1, HVF_CNTRL_1_VQR(0));
+
+	/* TODO enable pixel repeat for pixel rates less than 25Msamp/s */
+	rep = 0;
+	reg_write(encoder, REG_RPT_CNTRL, 0);
+	reg_write(encoder, REG_SEL_CLK, SEL_CLK_SEL_VRF_CLK(0) |
+			SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK);
+
+	reg_write(encoder, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(div) |
+			PLL_SERIAL_2_SRL_PR(rep));
+
+	reg_write16(encoder, REG_VS_PIX_STRT_2_MSB, pix_start2);
+	reg_write16(encoder, REG_VS_PIX_END_2_MSB, pix_start2);
+
+	/* set color matrix bypass flag: */
+	reg_set(encoder, REG_MAT_CONTRL, MAT_CONTRL_MAT_BP);
+
+	/* set BIAS tmds value: */
+	reg_write(encoder, REG_ANA_GENERAL, 0x09);
+
+	reg_clear(encoder, REG_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_MTHD);
+
+	reg_write(encoder, REG_VIP_CNTRL_3, 0);
+	reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_SYNC_HS);
+	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+		reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_V_TGL);
+
+	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+		reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_H_TGL);
+
+	reg_write(encoder, REG_VIDFORMAT, 0x00);
+	reg_write16(encoder, REG_NPIX_MSB, mode->hdisplay - 1);
+	reg_write16(encoder, REG_NLINE_MSB, mode->vdisplay - 1);
+	reg_write16(encoder, REG_VS_LINE_STRT_1_MSB, line_start);
+	reg_write16(encoder, REG_VS_LINE_END_1_MSB, line_end);
+	reg_write16(encoder, REG_VS_PIX_STRT_1_MSB, hs_start);
+	reg_write16(encoder, REG_VS_PIX_END_1_MSB, hs_start);
+	reg_write16(encoder, REG_HS_PIX_START_MSB, hs_start);
+	reg_write16(encoder, REG_HS_PIX_STOP_MSB, hs_end);
+	reg_write16(encoder, REG_VWIN_START_1_MSB, vwin_start);
+	reg_write16(encoder, REG_VWIN_END_1_MSB, vwin_end);
+	reg_write16(encoder, REG_DE_START_MSB, de_start);
+	reg_write16(encoder, REG_DE_STOP_MSB, de_end);
+
+	if (priv->rev == TDA19988) {
+		/* let incoming pixels fill the active space (if any) */
+		reg_write(encoder, REG_ENABLE_SPACE, 0x01);
+	}
+
+	reg_write16(encoder, REG_REFPIX_MSB, ref_pix);
+	reg_write16(encoder, REG_REFLINE_MSB, ref_line);
+
+	reg = TBG_CNTRL_1_VHX_EXT_DE |
+			TBG_CNTRL_1_VHX_EXT_HS |
+			TBG_CNTRL_1_VHX_EXT_VS |
+			TBG_CNTRL_1_DWIN_DIS | /* HDCP off */
+			TBG_CNTRL_1_VH_TGL_2;
+	if (mode->flags & (DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC))
+		reg |= TBG_CNTRL_1_VH_TGL_0;
+	reg_set(encoder, REG_TBG_CNTRL_1, reg);
+
+	/* must be last register set: */
+	reg_clear(encoder, REG_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_ONCE);
+}
+
+static enum drm_connector_status
+tda998x_encoder_detect(struct drm_encoder *encoder,
+		      struct drm_connector *connector)
+{
+	uint8_t val = cec_read(encoder, REG_CEC_RXSHPDLEV);
+	return (val & CEC_RXSHPDLEV_HPD) ? connector_status_connected :
+			connector_status_disconnected;
+}
+
+static int
+read_edid_block(struct drm_encoder *encoder, uint8_t *buf, int blk)
+{
+	uint8_t offset, segptr;
+	int ret, i;
+
+	/* enable EDID read irq: */
+	reg_set(encoder, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD);
+
+	offset = (blk & 1) ? 128 : 0;
+	segptr = blk / 2;
+
+	reg_write(encoder, REG_DDC_ADDR, 0xa0);
+	reg_write(encoder, REG_DDC_OFFS, offset);
+	reg_write(encoder, REG_DDC_SEGM_ADDR, 0x60);
+	reg_write(encoder, REG_DDC_SEGM, segptr);
+
+	/* enable reading EDID: */
+	reg_write(encoder, REG_EDID_CTRL, 0x1);
+
+	/* flag must be cleared by sw: */
+	reg_write(encoder, REG_EDID_CTRL, 0x0);
+
+	/* wait for block read to complete: */
+	for (i = 100; i > 0; i--) {
+		uint8_t val = reg_read(encoder, REG_INT_FLAGS_2);
+		if (val & INT_FLAGS_2_EDID_BLK_RD)
+			break;
+		msleep(1);
+	}
+
+	if (i == 0)
+		return -ETIMEDOUT;
+
+	ret = reg_read_range(encoder, REG_EDID_DATA_0, buf, EDID_LENGTH);
+	if (ret != EDID_LENGTH) {
+		dev_err(encoder->dev->dev, "failed to read edid block %d: %d",
+				blk, ret);
+		return ret;
+	}
+
+	reg_clear(encoder, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD);
+
+	return 0;
+}
+
+static uint8_t *
+do_get_edid(struct drm_encoder *encoder)
+{
+	int j = 0, valid_extensions = 0;
+	uint8_t *block, *new;
+	bool print_bad_edid = drm_debug & DRM_UT_KMS;
+
+	if ((block = kmalloc(EDID_LENGTH, GFP_KERNEL)) == NULL)
+		return NULL;
+
+	/* base block fetch */
+	if (read_edid_block(encoder, block, 0))
+		goto fail;
+
+	if (!drm_edid_block_valid(block, 0, print_bad_edid))
+		goto fail;
+
+	/* if there's no extensions, we're done */
+	if (block[0x7e] == 0)
+		return block;
+
+	new = krealloc(block, (block[0x7e] + 1) * EDID_LENGTH, GFP_KERNEL);
+	if (!new)
+		goto fail;
+	block = new;
+
+	for (j = 1; j <= block[0x7e]; j++) {
+		uint8_t *ext_block = block + (valid_extensions + 1) * EDID_LENGTH;
+		if (read_edid_block(encoder, ext_block, j))
+			goto fail;
+
+		if (!drm_edid_block_valid(ext_block, j, print_bad_edid))
+			goto fail;
+
+		valid_extensions++;
+	}
+
+	if (valid_extensions != block[0x7e]) {
+		block[EDID_LENGTH-1] += block[0x7e] - valid_extensions;
+		block[0x7e] = valid_extensions;
+		new = krealloc(block, (valid_extensions + 1) * EDID_LENGTH, GFP_KERNEL);
+		if (!new)
+			goto fail;
+		block = new;
+	}
+
+	return block;
+
+fail:
+	dev_warn(encoder->dev->dev, "failed to read EDID\n");
+	kfree(block);
+	return NULL;
+}
+
+static int
+tda998x_encoder_get_modes(struct drm_encoder *encoder,
+			 struct drm_connector *connector)
+{
+	struct edid *edid = (struct edid *)do_get_edid(encoder);
+	int n = 0;
+
+	if (edid) {
+		drm_mode_connector_update_edid_property(connector, edid);
+		n = drm_add_edid_modes(connector, edid);
+		kfree(edid);
+	}
+
+	return n;
+}
+
+static int
+tda998x_encoder_create_resources(struct drm_encoder *encoder,
+				struct drm_connector *connector)
+{
+	DBG("");
+	return 0;
+}
+
+static int
+tda998x_encoder_set_property(struct drm_encoder *encoder,
+			    struct drm_connector *connector,
+			    struct drm_property *property,
+			    uint64_t val)
+{
+	DBG("");
+	return 0;
+}
+
+static void
+tda998x_encoder_destroy(struct drm_encoder *encoder)
+{
+	struct tda998x_priv *priv = to_tda998x_priv(encoder);
+	drm_i2c_encoder_destroy(encoder);
+	kfree(priv);
+}
+
+static struct drm_encoder_slave_funcs tda998x_encoder_funcs = {
+	.set_config = tda998x_encoder_set_config,
+	.destroy = tda998x_encoder_destroy,
+	.dpms = tda998x_encoder_dpms,
+	.save = tda998x_encoder_save,
+	.restore = tda998x_encoder_restore,
+	.mode_fixup = tda998x_encoder_mode_fixup,
+	.mode_valid = tda998x_encoder_mode_valid,
+	.mode_set = tda998x_encoder_mode_set,
+	.detect = tda998x_encoder_detect,
+	.get_modes = tda998x_encoder_get_modes,
+	.create_resources = tda998x_encoder_create_resources,
+	.set_property = tda998x_encoder_set_property,
+};
+
+/* I2C driver functions */
+
+static int
+tda998x_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	return 0;
+}
+
+static int
+tda998x_remove(struct i2c_client *client)
+{
+	return 0;
+}
+
+static int
+tda998x_encoder_init(struct i2c_client *client,
+		    struct drm_device *dev,
+		    struct drm_encoder_slave *encoder_slave)
+{
+	struct drm_encoder *encoder = &encoder_slave->base;
+	struct tda998x_priv *priv;
+
+	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->current_page = 0;
+	priv->cec = i2c_new_dummy(client->adapter, 0x34);
+	priv->dpms = DRM_MODE_DPMS_OFF;
+
+	encoder_slave->slave_priv = priv;
+	encoder_slave->slave_funcs = &tda998x_encoder_funcs;
+
+	/* wake up the device: */
+	cec_write(encoder, REG_CEC_ENAMODS,
+			CEC_ENAMODS_EN_RXSENS | CEC_ENAMODS_EN_HDMI);
+
+	tda998x_reset(encoder);
+
+	/* read version: */
+	priv->rev = reg_read(encoder, REG_VERSION_LSB) |
+			reg_read(encoder, REG_VERSION_MSB) << 8;
+
+	/* mask off feature bits: */
+	priv->rev &= ~0x30; /* not-hdcp and not-scalar bit */
+
+	switch (priv->rev) {
+	case TDA9989N2:  dev_info(dev->dev, "found TDA9989 n2");  break;
+	case TDA19989:   dev_info(dev->dev, "found TDA19989");    break;
+	case TDA19989N2: dev_info(dev->dev, "found TDA19989 n2"); break;
+	case TDA19988:   dev_info(dev->dev, "found TDA19988");    break;
+	default:
+		DBG("found unsupported device: %04x", priv->rev);
+		goto fail;
+	}
+
+	/* after reset, enable DDC: */
+	reg_write(encoder, REG_DDC_DISABLE, 0x00);
+
+	/* set clock on DDC channel: */
+	reg_write(encoder, REG_TX3, 39);
+
+	/* if necessary, disable multi-master: */
+	if (priv->rev == TDA19989)
+		reg_set(encoder, REG_I2C_MASTER, I2C_MASTER_DIS_MM);
+
+	cec_write(encoder, REG_CEC_FRO_IM_CLK_CTRL,
+			CEC_FRO_IM_CLK_CTRL_GHOST_DIS | CEC_FRO_IM_CLK_CTRL_IMCLK_SEL);
+
+	return 0;
+
+fail:
+	/* if encoder_init fails, the encoder slave is never registered,
+	 * so cleanup here:
+	 */
+	if (priv->cec)
+		i2c_unregister_device(priv->cec);
+	kfree(priv);
+	encoder_slave->slave_priv = NULL;
+	encoder_slave->slave_funcs = NULL;
+	return -ENXIO;
+}
+
+static struct i2c_device_id tda998x_ids[] = {
+	{ "tda998x", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, tda998x_ids);
+
+static struct drm_i2c_encoder_driver tda998x_driver = {
+	.i2c_driver = {
+		.probe = tda998x_probe,
+		.remove = tda998x_remove,
+		.driver = {
+			.name = "tda998x",
+		},
+		.id_table = tda998x_ids,
+	},
+	.encoder_init = tda998x_encoder_init,
+};
+
+/* Module initialization */
+
+static int __init
+tda998x_init(void)
+{
+	DBG("");
+	return drm_i2c_encoder_register(THIS_MODULE, &tda998x_driver);
+}
+
+static void __exit
+tda998x_exit(void)
+{
+	DBG("");
+	drm_i2c_encoder_unregister(&tda998x_driver);
+}
+
+MODULE_DESCRIPTION("NXP Semiconductors TDA998X TMDS transmitter driver");
+
+MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
+MODULE_DESCRIPTION("NXP Semiconductors TDA998X HDMI Encoder");
+MODULE_LICENSE("GPL");
+
+module_init(tda998x_init);
+module_exit(tda998x_exit);
-- 
1.8.1


^ permalink raw reply related	[flat|nested] 66+ messages in thread

* [PATCH 2/4] drm/i2c: nxp-tda998x (v2)
@ 2013-01-22 22:36   ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-22 22:36 UTC (permalink / raw)
  To: linux-arm-kernel

Driver for the NXP TDA998X i2c hdmi encoder slave.

v1: original
v2: fix npix/nline programming

Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 drivers/gpu/drm/i2c/Makefile      |   3 +
 drivers/gpu/drm/i2c/tda998x_drv.c | 908 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 911 insertions(+)
 create mode 100644 drivers/gpu/drm/i2c/tda998x_drv.c

diff --git a/drivers/gpu/drm/i2c/Makefile b/drivers/gpu/drm/i2c/Makefile
index 9286256..43aa33b 100644
--- a/drivers/gpu/drm/i2c/Makefile
+++ b/drivers/gpu/drm/i2c/Makefile
@@ -5,3 +5,6 @@ obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o
 
 sil164-y := sil164_drv.o
 obj-$(CONFIG_DRM_I2C_SIL164) += sil164.o
+
+tda998x-y := tda998x_drv.o
+obj-$(CONFIG_DRM_I2C_NXP_TDA998X) += tda998x.o
diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
new file mode 100644
index 0000000..02054e8
--- /dev/null
+++ b/drivers/gpu/drm/i2c/tda998x_drv.c
@@ -0,0 +1,908 @@
+/*
+ * 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/module.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder_slave.h>
+#include <drm/drm_edid.h>
+
+
+#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
+
+struct tda998x_priv {
+	struct i2c_client *cec;
+	uint16_t rev;
+	uint8_t current_page;
+	int dpms;
+};
+
+#define to_tda998x_priv(x)  ((struct tda998x_priv *)to_encoder_slave(x)->slave_priv)
+
+/* The TDA9988 series of devices use a paged register scheme.. to simplify
+ * things we encode the page # in upper bits of the register #.  To read/
+ * write a given register, we need to make sure CURPAGE register is set
+ * appropriately.  Which implies reads/writes are not atomic.  Fun!
+ */
+
+#define REG(page, addr) (((page) << 8) | (addr))
+#define REG2ADDR(reg)   ((reg) & 0xff)
+#define REG2PAGE(reg)   (((reg) >> 8) & 0xff)
+
+#define REG_CURPAGE               0xff                /* write */
+
+
+/* Page 00h: General Control */
+#define REG_VERSION_LSB           REG(0x00, 0x00)     /* read */
+#define REG_MAIN_CNTRL0           REG(0x00, 0x01)     /* read/write */
+# define MAIN_CNTRL0_SR           (1 << 0)
+# define MAIN_CNTRL0_DECS         (1 << 1)
+# define MAIN_CNTRL0_DEHS         (1 << 2)
+# define MAIN_CNTRL0_CECS         (1 << 3)
+# define MAIN_CNTRL0_CEHS         (1 << 4)
+# define MAIN_CNTRL0_SCALER       (1 << 7)
+#define REG_VERSION_MSB           REG(0x00, 0x02)     /* read */
+#define REG_SOFTRESET             REG(0x00, 0x0a)     /* write */
+# define SOFTRESET_AUDIO          (1 << 0)
+# define SOFTRESET_I2C_MASTER     (1 << 1)
+#define REG_DDC_DISABLE           REG(0x00, 0x0b)     /* read/write */
+#define REG_CCLK_ON               REG(0x00, 0x0c)     /* read/write */
+#define REG_I2C_MASTER            REG(0x00, 0x0d)     /* read/write */
+# define I2C_MASTER_DIS_MM        (1 << 0)
+# define I2C_MASTER_DIS_FILT      (1 << 1)
+# define I2C_MASTER_APP_STRT_LAT  (1 << 2)
+#define REG_INT_FLAGS_0           REG(0x00, 0x0f)     /* read/write */
+#define REG_INT_FLAGS_1           REG(0x00, 0x10)     /* read/write */
+#define REG_INT_FLAGS_2           REG(0x00, 0x11)     /* read/write */
+# define INT_FLAGS_2_EDID_BLK_RD  (1 << 1)
+#define REG_ENA_VP_0              REG(0x00, 0x18)     /* read/write */
+#define REG_ENA_VP_1              REG(0x00, 0x19)     /* read/write */
+#define REG_ENA_VP_2              REG(0x00, 0x1a)     /* read/write */
+#define REG_ENA_AP                REG(0x00, 0x1e)     /* read/write */
+#define REG_VIP_CNTRL_0           REG(0x00, 0x20)     /* write */
+# define VIP_CNTRL_0_MIRR_A       (1 << 7)
+# define VIP_CNTRL_0_SWAP_A(x)    (((x) & 7) << 4)
+# define VIP_CNTRL_0_MIRR_B       (1 << 3)
+# define VIP_CNTRL_0_SWAP_B(x)    (((x) & 7) << 0)
+#define REG_VIP_CNTRL_1           REG(0x00, 0x21)     /* write */
+# define VIP_CNTRL_1_MIRR_C       (1 << 7)
+# define VIP_CNTRL_1_SWAP_C(x)    (((x) & 7) << 4)
+# define VIP_CNTRL_1_MIRR_D       (1 << 3)
+# define VIP_CNTRL_1_SWAP_D(x)    (((x) & 7) << 0)
+#define REG_VIP_CNTRL_2           REG(0x00, 0x22)     /* write */
+# define VIP_CNTRL_2_MIRR_E       (1 << 7)
+# define VIP_CNTRL_2_SWAP_E(x)    (((x) & 7) << 4)
+# define VIP_CNTRL_2_MIRR_F       (1 << 3)
+# define VIP_CNTRL_2_SWAP_F(x)    (((x) & 7) << 0)
+#define REG_VIP_CNTRL_3           REG(0x00, 0x23)     /* write */
+# define VIP_CNTRL_3_X_TGL        (1 << 0)
+# define VIP_CNTRL_3_H_TGL        (1 << 1)
+# define VIP_CNTRL_3_V_TGL        (1 << 2)
+# define VIP_CNTRL_3_EMB          (1 << 3)
+# define VIP_CNTRL_3_SYNC_DE      (1 << 4)
+# define VIP_CNTRL_3_SYNC_HS      (1 << 5)
+# define VIP_CNTRL_3_DE_INT       (1 << 6)
+# define VIP_CNTRL_3_EDGE         (1 << 7)
+#define REG_VIP_CNTRL_4           REG(0x00, 0x24)     /* write */
+# define VIP_CNTRL_4_BLC(x)       (((x) & 3) << 0)
+# define VIP_CNTRL_4_BLANKIT(x)   (((x) & 3) << 2)
+# define VIP_CNTRL_4_CCIR656      (1 << 4)
+# define VIP_CNTRL_4_656_ALT      (1 << 5)
+# define VIP_CNTRL_4_TST_656      (1 << 6)
+# define VIP_CNTRL_4_TST_PAT      (1 << 7)
+#define REG_VIP_CNTRL_5           REG(0x00, 0x25)     /* write */
+# define VIP_CNTRL_5_CKCASE       (1 << 0)
+# define VIP_CNTRL_5_SP_CNT(x)    (((x) & 3) << 1)
+#define REG_MAT_CONTRL            REG(0x00, 0x80)     /* write */
+# define MAT_CONTRL_MAT_SC(x)     (((x) & 3) << 0)
+# define MAT_CONTRL_MAT_BP        (1 << 2)
+#define REG_VIDFORMAT             REG(0x00, 0xa0)     /* write */
+#define REG_REFPIX_MSB            REG(0x00, 0xa1)     /* write */
+#define REG_REFPIX_LSB            REG(0x00, 0xa2)     /* write */
+#define REG_REFLINE_MSB           REG(0x00, 0xa3)     /* write */
+#define REG_REFLINE_LSB           REG(0x00, 0xa4)     /* write */
+#define REG_NPIX_MSB              REG(0x00, 0xa5)     /* write */
+#define REG_NPIX_LSB              REG(0x00, 0xa6)     /* write */
+#define REG_NLINE_MSB             REG(0x00, 0xa7)     /* write */
+#define REG_NLINE_LSB             REG(0x00, 0xa8)     /* write */
+#define REG_VS_LINE_STRT_1_MSB    REG(0x00, 0xa9)     /* write */
+#define REG_VS_LINE_STRT_1_LSB    REG(0x00, 0xaa)     /* write */
+#define REG_VS_PIX_STRT_1_MSB     REG(0x00, 0xab)     /* write */
+#define REG_VS_PIX_STRT_1_LSB     REG(0x00, 0xac)     /* write */
+#define REG_VS_LINE_END_1_MSB     REG(0x00, 0xad)     /* write */
+#define REG_VS_LINE_END_1_LSB     REG(0x00, 0xae)     /* write */
+#define REG_VS_PIX_END_1_MSB      REG(0x00, 0xaf)     /* write */
+#define REG_VS_PIX_END_1_LSB      REG(0x00, 0xb0)     /* write */
+#define REG_VS_PIX_STRT_2_MSB     REG(0x00, 0xb3)     /* write */
+#define REG_VS_PIX_STRT_2_LSB     REG(0x00, 0xb4)     /* write */
+#define REG_VS_PIX_END_2_MSB      REG(0x00, 0xb7)     /* write */
+#define REG_VS_PIX_END_2_LSB      REG(0x00, 0xb8)     /* write */
+#define REG_HS_PIX_START_MSB      REG(0x00, 0xb9)     /* write */
+#define REG_HS_PIX_START_LSB      REG(0x00, 0xba)     /* write */
+#define REG_HS_PIX_STOP_MSB       REG(0x00, 0xbb)     /* write */
+#define REG_HS_PIX_STOP_LSB       REG(0x00, 0xbc)     /* write */
+#define REG_VWIN_START_1_MSB      REG(0x00, 0xbd)     /* write */
+#define REG_VWIN_START_1_LSB      REG(0x00, 0xbe)     /* write */
+#define REG_VWIN_END_1_MSB        REG(0x00, 0xbf)     /* write */
+#define REG_VWIN_END_1_LSB        REG(0x00, 0xc0)     /* write */
+#define REG_DE_START_MSB          REG(0x00, 0xc5)     /* write */
+#define REG_DE_START_LSB          REG(0x00, 0xc6)     /* write */
+#define REG_DE_STOP_MSB           REG(0x00, 0xc7)     /* write */
+#define REG_DE_STOP_LSB           REG(0x00, 0xc8)     /* write */
+#define REG_TBG_CNTRL_0           REG(0x00, 0xca)     /* write */
+# define TBG_CNTRL_0_FRAME_DIS    (1 << 5)
+# define TBG_CNTRL_0_SYNC_MTHD    (1 << 6)
+# define TBG_CNTRL_0_SYNC_ONCE    (1 << 7)
+#define REG_TBG_CNTRL_1           REG(0x00, 0xcb)     /* write */
+# define TBG_CNTRL_1_VH_TGL_0     (1 << 0)
+# define TBG_CNTRL_1_VH_TGL_1     (1 << 1)
+# define TBG_CNTRL_1_VH_TGL_2     (1 << 2)
+# define TBG_CNTRL_1_VHX_EXT_DE   (1 << 3)
+# define TBG_CNTRL_1_VHX_EXT_HS   (1 << 4)
+# define TBG_CNTRL_1_VHX_EXT_VS   (1 << 5)
+# define TBG_CNTRL_1_DWIN_DIS     (1 << 6)
+#define REG_ENABLE_SPACE          REG(0x00, 0xd6)     /* write */
+#define REG_HVF_CNTRL_0           REG(0x00, 0xe4)     /* write */
+# define HVF_CNTRL_0_SM           (1 << 7)
+# define HVF_CNTRL_0_RWB          (1 << 6)
+# define HVF_CNTRL_0_PREFIL(x)    (((x) & 3) << 2)
+# define HVF_CNTRL_0_INTPOL(x)    (((x) & 3) << 0)
+#define REG_HVF_CNTRL_1           REG(0x00, 0xe5)     /* write */
+# define HVF_CNTRL_1_FOR          (1 << 0)
+# define HVF_CNTRL_1_YUVBLK       (1 << 1)
+# define HVF_CNTRL_1_VQR(x)       (((x) & 3) << 2)
+# define HVF_CNTRL_1_PAD(x)       (((x) & 3) << 4)
+# define HVF_CNTRL_1_SEMI_PLANAR  (1 << 6)
+#define REG_RPT_CNTRL             REG(0x00, 0xf0)     /* write */
+
+
+/* Page 02h: PLL settings */
+#define REG_PLL_SERIAL_1          REG(0x02, 0x00)     /* read/write */
+# define PLL_SERIAL_1_SRL_FDN     (1 << 0)
+# define PLL_SERIAL_1_SRL_IZ(x)   (((x) & 3) << 1)
+# define PLL_SERIAL_1_SRL_MAN_IZ  (1 << 6)
+#define REG_PLL_SERIAL_2          REG(0x02, 0x01)     /* read/write */
+# define PLL_SERIAL_2_SRL_NOSC(x) (((x) & 3) << 0)
+# define PLL_SERIAL_2_SRL_PR(x)   (((x) & 0xf) << 4)
+#define REG_PLL_SERIAL_3          REG(0x02, 0x02)     /* read/write */
+# define PLL_SERIAL_3_SRL_CCIR    (1 << 0)
+# define PLL_SERIAL_3_SRL_DE      (1 << 2)
+# define PLL_SERIAL_3_SRL_PXIN_SEL (1 << 4)
+#define REG_SERIALIZER            REG(0x02, 0x03)     /* read/write */
+#define REG_BUFFER_OUT            REG(0x02, 0x04)     /* read/write */
+#define REG_PLL_SCG1              REG(0x02, 0x05)     /* read/write */
+#define REG_PLL_SCG2              REG(0x02, 0x06)     /* read/write */
+#define REG_PLL_SCGN1             REG(0x02, 0x07)     /* read/write */
+#define REG_PLL_SCGN2             REG(0x02, 0x08)     /* read/write */
+#define REG_PLL_SCGR1             REG(0x02, 0x09)     /* read/write */
+#define REG_PLL_SCGR2             REG(0x02, 0x0a)     /* read/write */
+#define REG_AUDIO_DIV             REG(0x02, 0x0e)     /* read/write */
+#define REG_SEL_CLK               REG(0x02, 0x11)     /* read/write */
+# define SEL_CLK_SEL_CLK1         (1 << 0)
+# define SEL_CLK_SEL_VRF_CLK(x)   (((x) & 3) << 1)
+# define SEL_CLK_ENA_SC_CLK       (1 << 3)
+#define REG_ANA_GENERAL           REG(0x02, 0x12)     /* read/write */
+
+
+/* Page 09h: EDID Control */
+#define REG_EDID_DATA_0           REG(0x09, 0x00)     /* read */
+/* next 127 successive registers are the EDID block */
+#define REG_EDID_CTRL             REG(0x09, 0xfa)     /* read/write */
+#define REG_DDC_ADDR              REG(0x09, 0xfb)     /* read/write */
+#define REG_DDC_OFFS              REG(0x09, 0xfc)     /* read/write */
+#define REG_DDC_SEGM_ADDR         REG(0x09, 0xfd)     /* read/write */
+#define REG_DDC_SEGM              REG(0x09, 0xfe)     /* read/write */
+
+
+/* Page 10h: information frames and packets */
+
+
+/* Page 11h: audio settings and content info packets */
+#define REG_AIP_CNTRL_0           REG(0x11, 0x00)     /* read/write */
+# define AIP_CNTRL_0_RST_FIFO     (1 << 0)
+# define AIP_CNTRL_0_SWAP         (1 << 1)
+# define AIP_CNTRL_0_LAYOUT       (1 << 2)
+# define AIP_CNTRL_0_ACR_MAN      (1 << 5)
+# define AIP_CNTRL_0_RST_CTS      (1 << 6)
+#define REG_ENC_CNTRL             REG(0x11, 0x0d)     /* read/write */
+# define ENC_CNTRL_RST_ENC        (1 << 0)
+# define ENC_CNTRL_RST_SEL        (1 << 1)
+# define ENC_CNTRL_CTL_CODE(x)    (((x) & 3) << 2)
+
+
+/* Page 12h: HDCP and OTP */
+#define REG_TX3                   REG(0x12, 0x9a)     /* read/write */
+#define REG_TX33                  REG(0x12, 0xb8)     /* read/write */
+# define TX33_HDMI                (1 << 1)
+
+
+/* Page 13h: Gamut related metadata packets */
+
+
+
+/* CEC registers: (not paged)
+ */
+#define REG_CEC_FRO_IM_CLK_CTRL   0xfb                /* read/write */
+# define CEC_FRO_IM_CLK_CTRL_GHOST_DIS (1 << 7)
+# define CEC_FRO_IM_CLK_CTRL_ENA_OTP   (1 << 6)
+# define CEC_FRO_IM_CLK_CTRL_IMCLK_SEL (1 << 1)
+# define CEC_FRO_IM_CLK_CTRL_FRO_DIV   (1 << 0)
+#define REG_CEC_RXSHPDLEV         0xfe                /* read */
+# define CEC_RXSHPDLEV_RXSENS     (1 << 0)
+# define CEC_RXSHPDLEV_HPD        (1 << 1)
+
+#define REG_CEC_ENAMODS           0xff                /* read/write */
+# define CEC_ENAMODS_DIS_FRO      (1 << 6)
+# define CEC_ENAMODS_DIS_CCLK     (1 << 5)
+# define CEC_ENAMODS_EN_RXSENS    (1 << 2)
+# define CEC_ENAMODS_EN_HDMI      (1 << 1)
+# define CEC_ENAMODS_EN_CEC       (1 << 0)
+
+
+/* Device versions: */
+#define TDA9989N2                 0x0101
+#define TDA19989                  0x0201
+#define TDA19989N2                0x0202
+#define TDA19988                  0x0301
+
+static void
+cec_write(struct drm_encoder *encoder, uint16_t addr, uint8_t val)
+{
+	struct i2c_client *client = to_tda998x_priv(encoder)->cec;
+	uint8_t buf[] = {addr, val};
+	int ret;
+
+	ret = i2c_master_send(client, buf, ARRAY_SIZE(buf));
+	if (ret < 0)
+		dev_err(&client->dev, "Error %d writing to cec:0x%x\n", ret, addr);
+}
+
+static uint8_t
+cec_read(struct drm_encoder *encoder, uint8_t addr)
+{
+	struct i2c_client *client = to_tda998x_priv(encoder)->cec;
+	uint8_t val;
+	int ret;
+
+	ret = i2c_master_send(client, &addr, sizeof(addr));
+	if (ret < 0)
+		goto fail;
+
+	ret = i2c_master_recv(client, &val, sizeof(val));
+	if (ret < 0)
+		goto fail;
+
+	return val;
+
+fail:
+	dev_err(&client->dev, "Error %d reading from cec:0x%x\n", ret, addr);
+	return 0;
+}
+
+static void
+set_page(struct drm_encoder *encoder, uint16_t reg)
+{
+	struct tda998x_priv *priv = to_tda998x_priv(encoder);
+
+	if (REG2PAGE(reg) != priv->current_page) {
+		struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+		uint8_t buf[] = {
+				REG_CURPAGE, REG2PAGE(reg)
+		};
+		int ret = i2c_master_send(client, buf, sizeof(buf));
+		if (ret < 0)
+			dev_err(&client->dev, "Error %d writing to REG_CURPAGE\n", ret);
+
+		priv->current_page = REG2PAGE(reg);
+	}
+}
+
+static int
+reg_read_range(struct drm_encoder *encoder, uint16_t reg, char *buf, int cnt)
+{
+	struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+	uint8_t addr = REG2ADDR(reg);
+	int ret;
+
+	set_page(encoder, reg);
+
+	ret = i2c_master_send(client, &addr, sizeof(addr));
+	if (ret < 0)
+		goto fail;
+
+	ret = i2c_master_recv(client, buf, cnt);
+	if (ret < 0)
+		goto fail;
+
+	return ret;
+
+fail:
+	dev_err(&client->dev, "Error %d reading from 0x%x\n", ret, reg);
+	return ret;
+}
+
+static uint8_t
+reg_read(struct drm_encoder *encoder, uint16_t reg)
+{
+	uint8_t val = 0;
+	reg_read_range(encoder, reg, &val, sizeof(val));
+	return val;
+}
+
+static void
+reg_write(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
+{
+	struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+	uint8_t buf[] = {REG2ADDR(reg), val};
+	int ret;
+
+	set_page(encoder, reg);
+
+	ret = i2c_master_send(client, buf, ARRAY_SIZE(buf));
+	if (ret < 0)
+		dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg);
+}
+
+static void
+reg_write16(struct drm_encoder *encoder, uint16_t reg, uint16_t val)
+{
+	struct i2c_client *client = drm_i2c_encoder_get_client(encoder);
+	uint8_t buf[] = {REG2ADDR(reg), val >> 8, val};
+	int ret;
+
+	set_page(encoder, reg);
+
+	ret = i2c_master_send(client, buf, ARRAY_SIZE(buf));
+	if (ret < 0)
+		dev_err(&client->dev, "Error %d writing to 0x%x\n", ret, reg);
+}
+
+static void
+reg_set(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
+{
+	reg_write(encoder, reg, reg_read(encoder, reg) | val);
+}
+
+static void
+reg_clear(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
+{
+	reg_write(encoder, reg, reg_read(encoder, reg) & ~val);
+}
+
+static void
+tda998x_reset(struct drm_encoder *encoder)
+{
+	/* reset audio and i2c master: */
+	reg_set(encoder, REG_SOFTRESET, SOFTRESET_AUDIO | SOFTRESET_I2C_MASTER);
+	msleep(50);
+	reg_clear(encoder, REG_SOFTRESET, SOFTRESET_AUDIO | SOFTRESET_I2C_MASTER);
+	msleep(50);
+
+	/* reset transmitter: */
+	reg_set(encoder, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR);
+	reg_clear(encoder, REG_MAIN_CNTRL0, MAIN_CNTRL0_SR);
+
+	/* PLL registers common configuration */
+	reg_write(encoder, REG_PLL_SERIAL_1, 0x00);
+	reg_write(encoder, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(1));
+	reg_write(encoder, REG_PLL_SERIAL_3, 0x00);
+	reg_write(encoder, REG_SERIALIZER,   0x00);
+	reg_write(encoder, REG_BUFFER_OUT,   0x00);
+	reg_write(encoder, REG_PLL_SCG1,     0x00);
+	reg_write(encoder, REG_AUDIO_DIV,    0x03);
+	reg_write(encoder, REG_SEL_CLK,      SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK);
+	reg_write(encoder, REG_PLL_SCGN1,    0xfa);
+	reg_write(encoder, REG_PLL_SCGN2,    0x00);
+	reg_write(encoder, REG_PLL_SCGR1,    0x5b);
+	reg_write(encoder, REG_PLL_SCGR2,    0x00);
+	reg_write(encoder, REG_PLL_SCG2,     0x10);
+}
+
+/* DRM encoder functions */
+
+static void
+tda998x_encoder_set_config(struct drm_encoder *encoder, void *params)
+{
+}
+
+static void
+tda998x_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+	struct tda998x_priv *priv = to_tda998x_priv(encoder);
+
+	/* we only care about on or off: */
+	if (mode != DRM_MODE_DPMS_ON)
+		mode = DRM_MODE_DPMS_OFF;
+
+	if (mode == priv->dpms)
+		return;
+
+	switch (mode) {
+	case DRM_MODE_DPMS_ON:
+		/* enable audio and video ports */
+		reg_write(encoder, REG_ENA_AP, 0xff);
+		reg_write(encoder, REG_ENA_VP_0, 0xff);
+		reg_write(encoder, REG_ENA_VP_1, 0xff);
+		reg_write(encoder, REG_ENA_VP_2, 0xff);
+		/* set muxing after enabling ports: */
+		reg_write(encoder, REG_VIP_CNTRL_0,
+				VIP_CNTRL_0_SWAP_A(2) | VIP_CNTRL_0_SWAP_B(3));
+		reg_write(encoder, REG_VIP_CNTRL_1,
+				VIP_CNTRL_1_SWAP_C(0) | VIP_CNTRL_1_SWAP_D(1));
+		reg_write(encoder, REG_VIP_CNTRL_2,
+				VIP_CNTRL_2_SWAP_E(4) | VIP_CNTRL_2_SWAP_F(5));
+		break;
+	case DRM_MODE_DPMS_OFF:
+		/* disable audio and video ports */
+		reg_write(encoder, REG_ENA_AP, 0x00);
+		reg_write(encoder, REG_ENA_VP_0, 0x00);
+		reg_write(encoder, REG_ENA_VP_1, 0x00);
+		reg_write(encoder, REG_ENA_VP_2, 0x00);
+		break;
+	}
+
+	priv->dpms = mode;
+}
+
+static void
+tda998x_encoder_save(struct drm_encoder *encoder)
+{
+	DBG("");
+}
+
+static void
+tda998x_encoder_restore(struct drm_encoder *encoder)
+{
+	DBG("");
+}
+
+static bool
+tda998x_encoder_mode_fixup(struct drm_encoder *encoder,
+			  const struct drm_display_mode *mode,
+			  struct drm_display_mode *adjusted_mode)
+{
+	return true;
+}
+
+static int
+tda998x_encoder_mode_valid(struct drm_encoder *encoder,
+			  struct drm_display_mode *mode)
+{
+	return MODE_OK;
+}
+
+static void
+tda998x_encoder_mode_set(struct drm_encoder *encoder,
+			struct drm_display_mode *mode,
+			struct drm_display_mode *adjusted_mode)
+{
+	struct tda998x_priv *priv = to_tda998x_priv(encoder);
+	uint16_t hs_start, hs_end, line_start, line_end;
+	uint16_t vwin_start, vwin_end, de_start, de_end;
+	uint16_t ref_pix, ref_line, pix_start2;
+	uint8_t reg, div, rep;
+
+	hs_start   = mode->hsync_start - mode->hdisplay;
+	hs_end     = mode->hsync_end - mode->hdisplay;
+	line_start = 1;
+	line_end   = 1 + mode->vsync_end - mode->vsync_start;
+	vwin_start = mode->vtotal - mode->vsync_start;
+	vwin_end   = vwin_start + mode->vdisplay;
+	de_start   = mode->htotal - mode->hdisplay;
+	de_end     = mode->htotal;
+
+	pix_start2 = 0;
+	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+		pix_start2 = (mode->htotal / 2) + hs_start;
+
+	/* TODO how is this value calculated?  It is 2 for all common
+	 * formats in the tables in out of tree nxp driver (assuming
+	 * I've properly deciphered their byzantine table system)
+	 */
+	ref_line = 2;
+
+	/* this might changes for other color formats from the CRTC: */
+	ref_pix = 3 + hs_start;
+
+	div = 148500 / mode->clock;
+
+	DBG("clock=%d, div=%u", mode->clock, div);
+	DBG("hs_start=%u, hs_end=%u, line_start=%u, line_end=%u",
+			hs_start, hs_end, line_start, line_end);
+	DBG("vwin_start=%u, vwin_end=%u, de_start=%u, de_end=%u",
+			vwin_start, vwin_end, de_start, de_end);
+	DBG("ref_line=%u, ref_pix=%u, pix_start2=%u",
+			ref_line, ref_pix, pix_start2);
+
+	/* mute the audio FIFO: */
+	reg_set(encoder, REG_AIP_CNTRL_0, AIP_CNTRL_0_RST_FIFO);
+
+	/* set HDMI HDCP mode off: */
+	reg_set(encoder, REG_TBG_CNTRL_1, TBG_CNTRL_1_DWIN_DIS);
+	reg_clear(encoder, REG_TX33, TX33_HDMI);
+
+	reg_write(encoder, REG_ENC_CNTRL, ENC_CNTRL_CTL_CODE(0));
+	/* no pre-filter or interpolator: */
+	reg_write(encoder, REG_HVF_CNTRL_0, HVF_CNTRL_0_PREFIL(0) |
+			HVF_CNTRL_0_INTPOL(0));
+	reg_write(encoder, REG_VIP_CNTRL_5, VIP_CNTRL_5_SP_CNT(0));
+	reg_write(encoder, REG_VIP_CNTRL_4, VIP_CNTRL_4_BLANKIT(0) |
+			VIP_CNTRL_4_BLC(0));
+	reg_clear(encoder, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_CCIR);
+
+	reg_clear(encoder, REG_PLL_SERIAL_1, PLL_SERIAL_1_SRL_MAN_IZ);
+	reg_clear(encoder, REG_PLL_SERIAL_3, PLL_SERIAL_3_SRL_DE);
+	reg_write(encoder, REG_SERIALIZER, 0);
+	reg_write(encoder, REG_HVF_CNTRL_1, HVF_CNTRL_1_VQR(0));
+
+	/* TODO enable pixel repeat for pixel rates less than 25Msamp/s */
+	rep = 0;
+	reg_write(encoder, REG_RPT_CNTRL, 0);
+	reg_write(encoder, REG_SEL_CLK, SEL_CLK_SEL_VRF_CLK(0) |
+			SEL_CLK_SEL_CLK1 | SEL_CLK_ENA_SC_CLK);
+
+	reg_write(encoder, REG_PLL_SERIAL_2, PLL_SERIAL_2_SRL_NOSC(div) |
+			PLL_SERIAL_2_SRL_PR(rep));
+
+	reg_write16(encoder, REG_VS_PIX_STRT_2_MSB, pix_start2);
+	reg_write16(encoder, REG_VS_PIX_END_2_MSB, pix_start2);
+
+	/* set color matrix bypass flag: */
+	reg_set(encoder, REG_MAT_CONTRL, MAT_CONTRL_MAT_BP);
+
+	/* set BIAS tmds value: */
+	reg_write(encoder, REG_ANA_GENERAL, 0x09);
+
+	reg_clear(encoder, REG_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_MTHD);
+
+	reg_write(encoder, REG_VIP_CNTRL_3, 0);
+	reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_SYNC_HS);
+	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
+		reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_V_TGL);
+
+	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
+		reg_set(encoder, REG_VIP_CNTRL_3, VIP_CNTRL_3_H_TGL);
+
+	reg_write(encoder, REG_VIDFORMAT, 0x00);
+	reg_write16(encoder, REG_NPIX_MSB, mode->hdisplay - 1);
+	reg_write16(encoder, REG_NLINE_MSB, mode->vdisplay - 1);
+	reg_write16(encoder, REG_VS_LINE_STRT_1_MSB, line_start);
+	reg_write16(encoder, REG_VS_LINE_END_1_MSB, line_end);
+	reg_write16(encoder, REG_VS_PIX_STRT_1_MSB, hs_start);
+	reg_write16(encoder, REG_VS_PIX_END_1_MSB, hs_start);
+	reg_write16(encoder, REG_HS_PIX_START_MSB, hs_start);
+	reg_write16(encoder, REG_HS_PIX_STOP_MSB, hs_end);
+	reg_write16(encoder, REG_VWIN_START_1_MSB, vwin_start);
+	reg_write16(encoder, REG_VWIN_END_1_MSB, vwin_end);
+	reg_write16(encoder, REG_DE_START_MSB, de_start);
+	reg_write16(encoder, REG_DE_STOP_MSB, de_end);
+
+	if (priv->rev == TDA19988) {
+		/* let incoming pixels fill the active space (if any) */
+		reg_write(encoder, REG_ENABLE_SPACE, 0x01);
+	}
+
+	reg_write16(encoder, REG_REFPIX_MSB, ref_pix);
+	reg_write16(encoder, REG_REFLINE_MSB, ref_line);
+
+	reg = TBG_CNTRL_1_VHX_EXT_DE |
+			TBG_CNTRL_1_VHX_EXT_HS |
+			TBG_CNTRL_1_VHX_EXT_VS |
+			TBG_CNTRL_1_DWIN_DIS | /* HDCP off */
+			TBG_CNTRL_1_VH_TGL_2;
+	if (mode->flags & (DRM_MODE_FLAG_NVSYNC | DRM_MODE_FLAG_NHSYNC))
+		reg |= TBG_CNTRL_1_VH_TGL_0;
+	reg_set(encoder, REG_TBG_CNTRL_1, reg);
+
+	/* must be last register set: */
+	reg_clear(encoder, REG_TBG_CNTRL_0, TBG_CNTRL_0_SYNC_ONCE);
+}
+
+static enum drm_connector_status
+tda998x_encoder_detect(struct drm_encoder *encoder,
+		      struct drm_connector *connector)
+{
+	uint8_t val = cec_read(encoder, REG_CEC_RXSHPDLEV);
+	return (val & CEC_RXSHPDLEV_HPD) ? connector_status_connected :
+			connector_status_disconnected;
+}
+
+static int
+read_edid_block(struct drm_encoder *encoder, uint8_t *buf, int blk)
+{
+	uint8_t offset, segptr;
+	int ret, i;
+
+	/* enable EDID read irq: */
+	reg_set(encoder, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD);
+
+	offset = (blk & 1) ? 128 : 0;
+	segptr = blk / 2;
+
+	reg_write(encoder, REG_DDC_ADDR, 0xa0);
+	reg_write(encoder, REG_DDC_OFFS, offset);
+	reg_write(encoder, REG_DDC_SEGM_ADDR, 0x60);
+	reg_write(encoder, REG_DDC_SEGM, segptr);
+
+	/* enable reading EDID: */
+	reg_write(encoder, REG_EDID_CTRL, 0x1);
+
+	/* flag must be cleared by sw: */
+	reg_write(encoder, REG_EDID_CTRL, 0x0);
+
+	/* wait for block read to complete: */
+	for (i = 100; i > 0; i--) {
+		uint8_t val = reg_read(encoder, REG_INT_FLAGS_2);
+		if (val & INT_FLAGS_2_EDID_BLK_RD)
+			break;
+		msleep(1);
+	}
+
+	if (i == 0)
+		return -ETIMEDOUT;
+
+	ret = reg_read_range(encoder, REG_EDID_DATA_0, buf, EDID_LENGTH);
+	if (ret != EDID_LENGTH) {
+		dev_err(encoder->dev->dev, "failed to read edid block %d: %d",
+				blk, ret);
+		return ret;
+	}
+
+	reg_clear(encoder, REG_INT_FLAGS_2, INT_FLAGS_2_EDID_BLK_RD);
+
+	return 0;
+}
+
+static uint8_t *
+do_get_edid(struct drm_encoder *encoder)
+{
+	int j = 0, valid_extensions = 0;
+	uint8_t *block, *new;
+	bool print_bad_edid = drm_debug & DRM_UT_KMS;
+
+	if ((block = kmalloc(EDID_LENGTH, GFP_KERNEL)) == NULL)
+		return NULL;
+
+	/* base block fetch */
+	if (read_edid_block(encoder, block, 0))
+		goto fail;
+
+	if (!drm_edid_block_valid(block, 0, print_bad_edid))
+		goto fail;
+
+	/* if there's no extensions, we're done */
+	if (block[0x7e] == 0)
+		return block;
+
+	new = krealloc(block, (block[0x7e] + 1) * EDID_LENGTH, GFP_KERNEL);
+	if (!new)
+		goto fail;
+	block = new;
+
+	for (j = 1; j <= block[0x7e]; j++) {
+		uint8_t *ext_block = block + (valid_extensions + 1) * EDID_LENGTH;
+		if (read_edid_block(encoder, ext_block, j))
+			goto fail;
+
+		if (!drm_edid_block_valid(ext_block, j, print_bad_edid))
+			goto fail;
+
+		valid_extensions++;
+	}
+
+	if (valid_extensions != block[0x7e]) {
+		block[EDID_LENGTH-1] += block[0x7e] - valid_extensions;
+		block[0x7e] = valid_extensions;
+		new = krealloc(block, (valid_extensions + 1) * EDID_LENGTH, GFP_KERNEL);
+		if (!new)
+			goto fail;
+		block = new;
+	}
+
+	return block;
+
+fail:
+	dev_warn(encoder->dev->dev, "failed to read EDID\n");
+	kfree(block);
+	return NULL;
+}
+
+static int
+tda998x_encoder_get_modes(struct drm_encoder *encoder,
+			 struct drm_connector *connector)
+{
+	struct edid *edid = (struct edid *)do_get_edid(encoder);
+	int n = 0;
+
+	if (edid) {
+		drm_mode_connector_update_edid_property(connector, edid);
+		n = drm_add_edid_modes(connector, edid);
+		kfree(edid);
+	}
+
+	return n;
+}
+
+static int
+tda998x_encoder_create_resources(struct drm_encoder *encoder,
+				struct drm_connector *connector)
+{
+	DBG("");
+	return 0;
+}
+
+static int
+tda998x_encoder_set_property(struct drm_encoder *encoder,
+			    struct drm_connector *connector,
+			    struct drm_property *property,
+			    uint64_t val)
+{
+	DBG("");
+	return 0;
+}
+
+static void
+tda998x_encoder_destroy(struct drm_encoder *encoder)
+{
+	struct tda998x_priv *priv = to_tda998x_priv(encoder);
+	drm_i2c_encoder_destroy(encoder);
+	kfree(priv);
+}
+
+static struct drm_encoder_slave_funcs tda998x_encoder_funcs = {
+	.set_config = tda998x_encoder_set_config,
+	.destroy = tda998x_encoder_destroy,
+	.dpms = tda998x_encoder_dpms,
+	.save = tda998x_encoder_save,
+	.restore = tda998x_encoder_restore,
+	.mode_fixup = tda998x_encoder_mode_fixup,
+	.mode_valid = tda998x_encoder_mode_valid,
+	.mode_set = tda998x_encoder_mode_set,
+	.detect = tda998x_encoder_detect,
+	.get_modes = tda998x_encoder_get_modes,
+	.create_resources = tda998x_encoder_create_resources,
+	.set_property = tda998x_encoder_set_property,
+};
+
+/* I2C driver functions */
+
+static int
+tda998x_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+	return 0;
+}
+
+static int
+tda998x_remove(struct i2c_client *client)
+{
+	return 0;
+}
+
+static int
+tda998x_encoder_init(struct i2c_client *client,
+		    struct drm_device *dev,
+		    struct drm_encoder_slave *encoder_slave)
+{
+	struct drm_encoder *encoder = &encoder_slave->base;
+	struct tda998x_priv *priv;
+
+	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->current_page = 0;
+	priv->cec = i2c_new_dummy(client->adapter, 0x34);
+	priv->dpms = DRM_MODE_DPMS_OFF;
+
+	encoder_slave->slave_priv = priv;
+	encoder_slave->slave_funcs = &tda998x_encoder_funcs;
+
+	/* wake up the device: */
+	cec_write(encoder, REG_CEC_ENAMODS,
+			CEC_ENAMODS_EN_RXSENS | CEC_ENAMODS_EN_HDMI);
+
+	tda998x_reset(encoder);
+
+	/* read version: */
+	priv->rev = reg_read(encoder, REG_VERSION_LSB) |
+			reg_read(encoder, REG_VERSION_MSB) << 8;
+
+	/* mask off feature bits: */
+	priv->rev &= ~0x30; /* not-hdcp and not-scalar bit */
+
+	switch (priv->rev) {
+	case TDA9989N2:  dev_info(dev->dev, "found TDA9989 n2");  break;
+	case TDA19989:   dev_info(dev->dev, "found TDA19989");    break;
+	case TDA19989N2: dev_info(dev->dev, "found TDA19989 n2"); break;
+	case TDA19988:   dev_info(dev->dev, "found TDA19988");    break;
+	default:
+		DBG("found unsupported device: %04x", priv->rev);
+		goto fail;
+	}
+
+	/* after reset, enable DDC: */
+	reg_write(encoder, REG_DDC_DISABLE, 0x00);
+
+	/* set clock on DDC channel: */
+	reg_write(encoder, REG_TX3, 39);
+
+	/* if necessary, disable multi-master: */
+	if (priv->rev == TDA19989)
+		reg_set(encoder, REG_I2C_MASTER, I2C_MASTER_DIS_MM);
+
+	cec_write(encoder, REG_CEC_FRO_IM_CLK_CTRL,
+			CEC_FRO_IM_CLK_CTRL_GHOST_DIS | CEC_FRO_IM_CLK_CTRL_IMCLK_SEL);
+
+	return 0;
+
+fail:
+	/* if encoder_init fails, the encoder slave is never registered,
+	 * so cleanup here:
+	 */
+	if (priv->cec)
+		i2c_unregister_device(priv->cec);
+	kfree(priv);
+	encoder_slave->slave_priv = NULL;
+	encoder_slave->slave_funcs = NULL;
+	return -ENXIO;
+}
+
+static struct i2c_device_id tda998x_ids[] = {
+	{ "tda998x", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, tda998x_ids);
+
+static struct drm_i2c_encoder_driver tda998x_driver = {
+	.i2c_driver = {
+		.probe = tda998x_probe,
+		.remove = tda998x_remove,
+		.driver = {
+			.name = "tda998x",
+		},
+		.id_table = tda998x_ids,
+	},
+	.encoder_init = tda998x_encoder_init,
+};
+
+/* Module initialization */
+
+static int __init
+tda998x_init(void)
+{
+	DBG("");
+	return drm_i2c_encoder_register(THIS_MODULE, &tda998x_driver);
+}
+
+static void __exit
+tda998x_exit(void)
+{
+	DBG("");
+	drm_i2c_encoder_unregister(&tda998x_driver);
+}
+
+MODULE_DESCRIPTION("NXP Semiconductors TDA998X TMDS transmitter driver");
+
+MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
+MODULE_DESCRIPTION("NXP Semiconductors TDA998X HDMI Encoder");
+MODULE_LICENSE("GPL");
+
+module_init(tda998x_init);
+module_exit(tda998x_exit);
-- 
1.8.1

^ permalink raw reply related	[flat|nested] 66+ messages in thread

* [PATCH 3/4] drm/tilcdc: add encoder slave
  2013-01-22 22:36 ` Rob Clark
@ 2013-01-22 22:36   ` Rob Clark
  -1 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-22 22:36 UTC (permalink / raw)
  To: dri-devel; +Cc: patches, linux-omap, linux-arm-kernel, Rob Clark

Add output panel driver for i2c encoder slaves.

Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 drivers/gpu/drm/tilcdc/Kconfig        |  12 ++
 drivers/gpu/drm/tilcdc/Makefile       |   1 +
 drivers/gpu/drm/tilcdc/tilcdc_drv.c   |   5 +-
 drivers/gpu/drm/tilcdc/tilcdc_slave.c | 380 ++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_slave.h |  26 +++
 5 files changed, 423 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.h

diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig
index ee9b592..99beca2 100644
--- a/drivers/gpu/drm/tilcdc/Kconfig
+++ b/drivers/gpu/drm/tilcdc/Kconfig
@@ -8,3 +8,15 @@ config DRM_TILCDC
 	  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.
+
+menu "I2C encoder or helper chips"
+	depends on DRM && DRM_KMS_HELPER && I2C
+
+config DRM_I2C_NXP_TDA998X
+	tristate "NXP Semiconductors TDA998X HDMI encoder"
+	default m if DRM_TILCDC
+	help
+	  Support for NXP Semiconductors TDA998X HDMI encoders.
+
+endmenu
+
diff --git a/drivers/gpu/drm/tilcdc/Makefile b/drivers/gpu/drm/tilcdc/Makefile
index 1359cc2..aa9097e 100644
--- a/drivers/gpu/drm/tilcdc/Makefile
+++ b/drivers/gpu/drm/tilcdc/Makefile
@@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm -Werror
 tilcdc-y := \
 	tilcdc_crtc.o \
 	tilcdc_tfp410.o \
+	tilcdc_slave.o \
 	tilcdc_drv.o
 
 obj-$(CONFIG_DRM_TILCDC)	+= tilcdc.o
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
index cf1fddc..ca76dbe 100644
--- a/drivers/gpu/drm/tilcdc/tilcdc_drv.c
+++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
@@ -20,6 +20,7 @@
 #include "tilcdc_drv.h"
 #include "tilcdc_regs.h"
 #include "tilcdc_tfp410.h"
+#include "tilcdc_slave.h"
 
 #include "drm_fb_helper.h"
 
@@ -587,6 +588,7 @@ static int __init tilcdc_drm_init(void)
 {
 	DBG("init");
 	tilcdc_tfp410_init();
+	tilcdc_slave_init();
 	return platform_driver_register(&tilcdc_platform_driver);
 }
 
@@ -594,10 +596,11 @@ static void __exit tilcdc_drm_fini(void)
 {
 	DBG("fini");
 	tilcdc_tfp410_fini();
+	tilcdc_slave_fini();
 	platform_driver_unregister(&tilcdc_platform_driver);
 }
 
-module_init(tilcdc_drm_init);
+late_initcall(tilcdc_drm_init);
 module_exit(tilcdc_drm_fini);
 
 MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave.c b/drivers/gpu/drm/tilcdc/tilcdc_slave.c
new file mode 100644
index 0000000..b6f3e63
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_slave.c
@@ -0,0 +1,380 @@
+/*
+ * 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/pinctrl/pinmux.h>
+#include <linux/pinctrl/consumer.h>
+#include <drm/drm_encoder_slave.h>
+
+#include "tilcdc_drv.h"
+
+struct slave_module {
+	struct tilcdc_module base;
+	struct i2c_adapter *i2c;
+};
+#define to_slave_module(x) container_of(x, struct slave_module, base)
+
+static const struct tilcdc_panel_info slave_info = {
+		.min_bpp                = 16,
+		.max_bpp                = 16,
+		.bpp                    = 16,
+		.ac_bias                = 255,
+		.ac_bias_intrpt         = 0,
+		.dma_burst_sz           = 16,
+		.fdd                    = 0x80,
+		.tft_alt_mode           = 0,
+		.stn_565_mode           = 0,
+		.mono_8bit_mode         = 0,
+		.sync_edge              = 0,
+		.sync_ctrl              = 1,
+		.raster_order           = 0,
+};
+
+
+/*
+ * Encoder:
+ */
+
+struct slave_encoder {
+	struct drm_encoder_slave base;
+	struct slave_module *mod;
+};
+#define to_slave_encoder(x) container_of(to_encoder_slave(x), struct slave_encoder, base)
+
+static inline struct drm_encoder_slave_funcs *
+get_slave_funcs(struct drm_encoder *enc)
+{
+	return to_encoder_slave(enc)->slave_funcs;
+}
+
+static void slave_encoder_destroy(struct drm_encoder *encoder)
+{
+	struct slave_encoder *slave_encoder = to_slave_encoder(encoder);
+	if (get_slave_funcs(encoder))
+		get_slave_funcs(encoder)->destroy(encoder);
+	drm_encoder_cleanup(encoder);
+	kfree(slave_encoder);
+}
+
+static void slave_encoder_prepare(struct drm_encoder *encoder)
+{
+	drm_i2c_encoder_prepare(encoder);
+	tilcdc_crtc_set_panel_info(encoder->crtc, &slave_info);
+}
+
+static const struct drm_encoder_funcs slave_encoder_funcs = {
+		.destroy        = slave_encoder_destroy,
+};
+
+static const struct drm_encoder_helper_funcs slave_encoder_helper_funcs = {
+		.dpms           = drm_i2c_encoder_dpms,
+		.mode_fixup     = drm_i2c_encoder_mode_fixup,
+		.prepare        = slave_encoder_prepare,
+		.commit         = drm_i2c_encoder_commit,
+		.mode_set       = drm_i2c_encoder_mode_set,
+		.save           = drm_i2c_encoder_save,
+		.restore        = drm_i2c_encoder_restore,
+};
+
+static const struct i2c_board_info info = {
+		I2C_BOARD_INFO("tda998x", 0x70)
+};
+
+static struct drm_encoder *slave_encoder_create(struct drm_device *dev,
+		struct slave_module *mod)
+{
+	struct slave_encoder *slave_encoder;
+	struct drm_encoder *encoder;
+	int ret;
+
+	slave_encoder = kzalloc(sizeof(*slave_encoder), GFP_KERNEL);
+	if (!slave_encoder) {
+		dev_err(dev->dev, "allocation failed\n");
+		return NULL;
+	}
+
+	slave_encoder->mod = mod;
+
+	encoder = &slave_encoder->base.base;
+	encoder->possible_crtcs = 1;
+
+	ret = drm_encoder_init(dev, encoder, &slave_encoder_funcs,
+			DRM_MODE_ENCODER_LVDS);
+	if (ret)
+		goto fail;
+
+	drm_encoder_helper_add(encoder, &slave_encoder_helper_funcs);
+
+	ret = drm_i2c_encoder_init(dev, to_encoder_slave(encoder), mod->i2c, &info);
+	if (ret)
+		goto fail;
+
+	return encoder;
+
+fail:
+	slave_encoder_destroy(encoder);
+	return NULL;
+}
+
+/*
+ * Connector:
+ */
+
+struct slave_connector {
+	struct drm_connector base;
+
+	struct drm_encoder *encoder;  /* our connected encoder */
+	struct slave_module *mod;
+};
+#define to_slave_connector(x) container_of(x, struct slave_connector, base)
+
+static void slave_connector_destroy(struct drm_connector *connector)
+{
+	struct slave_connector *slave_connector = to_slave_connector(connector);
+	drm_connector_cleanup(connector);
+	kfree(slave_connector);
+}
+
+static enum drm_connector_status slave_connector_detect(
+		struct drm_connector *connector,
+		bool force)
+{
+	struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
+	return get_slave_funcs(encoder)->detect(encoder, connector);
+}
+
+static int slave_connector_get_modes(struct drm_connector *connector)
+{
+	struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
+	return get_slave_funcs(encoder)->get_modes(encoder, connector);
+}
+
+static int slave_connector_mode_valid(struct drm_connector *connector,
+		  struct drm_display_mode *mode)
+{
+	struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
+	struct tilcdc_drm_private *priv = connector->dev->dev_private;
+	int ret;
+
+	ret = tilcdc_crtc_mode_valid(priv->crtc, mode);
+	if (ret != MODE_OK)
+		return ret;
+
+	return get_slave_funcs(encoder)->mode_valid(encoder, mode);
+}
+
+static struct drm_encoder *slave_connector_best_encoder(
+		struct drm_connector *connector)
+{
+	struct slave_connector *slave_connector = to_slave_connector(connector);
+	return slave_connector->encoder;
+}
+
+static int slave_connector_set_property(struct drm_connector *connector,
+		struct drm_property *property, uint64_t value)
+{
+	struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
+	return get_slave_funcs(encoder)->set_property(encoder,
+			connector, property, value);
+}
+
+static const struct drm_connector_funcs slave_connector_funcs = {
+	.destroy            = slave_connector_destroy,
+	.dpms               = drm_helper_connector_dpms,
+	.detect             = slave_connector_detect,
+	.fill_modes         = drm_helper_probe_single_connector_modes,
+	.set_property       = slave_connector_set_property,
+};
+
+static const struct drm_connector_helper_funcs slave_connector_helper_funcs = {
+	.get_modes          = slave_connector_get_modes,
+	.mode_valid         = slave_connector_mode_valid,
+	.best_encoder       = slave_connector_best_encoder,
+};
+
+static struct drm_connector *slave_connector_create(struct drm_device *dev,
+		struct slave_module *mod, struct drm_encoder *encoder)
+{
+	struct slave_connector *slave_connector;
+	struct drm_connector *connector;
+	int ret;
+
+	slave_connector = kzalloc(sizeof(*slave_connector), GFP_KERNEL);
+	if (!slave_connector) {
+		dev_err(dev->dev, "allocation failed\n");
+		return NULL;
+	}
+
+	slave_connector->encoder = encoder;
+	slave_connector->mod = mod;
+
+	connector = &slave_connector->base;
+
+	drm_connector_init(dev, connector, &slave_connector_funcs,
+			DRM_MODE_CONNECTOR_HDMIA);
+	drm_connector_helper_add(connector, &slave_connector_helper_funcs);
+
+	connector->polled = DRM_CONNECTOR_POLL_CONNECT |
+			DRM_CONNECTOR_POLL_DISCONNECT;
+
+	connector->interlace_allowed = 0;
+	connector->doublescan_allowed = 0;
+
+	get_slave_funcs(encoder)->create_resources(encoder, connector);
+
+	ret = drm_mode_connector_attach_encoder(connector, encoder);
+	if (ret)
+		goto fail;
+
+	drm_sysfs_connector_add(connector);
+
+	return connector;
+
+fail:
+	slave_connector_destroy(connector);
+	return NULL;
+}
+
+/*
+ * Module:
+ */
+
+static int slave_modeset_init(struct tilcdc_module *mod, struct drm_device *dev)
+{
+	struct slave_module *slave_mod = to_slave_module(mod);
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	struct drm_encoder *encoder;
+	struct drm_connector *connector;
+
+	encoder = slave_encoder_create(dev, slave_mod);
+	if (!encoder)
+		return -ENOMEM;
+
+	connector = slave_connector_create(dev, slave_mod, encoder);
+	if (!connector)
+		return -ENOMEM;
+
+	priv->encoders[priv->num_encoders++] = encoder;
+	priv->connectors[priv->num_connectors++] = connector;
+
+	return 0;
+}
+
+static void slave_destroy(struct tilcdc_module *mod)
+{
+	struct slave_module *slave_mod = to_slave_module(mod);
+
+	tilcdc_module_cleanup(mod);
+	kfree(slave_mod);
+}
+
+static const struct tilcdc_module_ops slave_module_ops = {
+		.modeset_init = slave_modeset_init,
+		.destroy = slave_destroy,
+};
+
+/*
+ * Device:
+ */
+
+static struct of_device_id slave_of_match[];
+
+static int slave_probe(struct platform_device *pdev)
+{
+	struct device_node *node = pdev->dev.of_node;
+	struct device_node *i2c_node;
+	struct slave_module *slave_mod;
+	struct tilcdc_module *mod;
+	struct pinctrl *pinctrl;
+	uint32_t i2c_phandle;
+	int ret = -EINVAL;
+
+	/* bail out early if no DT data: */
+	if (!node) {
+		dev_err(&pdev->dev, "device-tree data is missing\n");
+		return -ENXIO;
+	}
+
+	slave_mod = kzalloc(sizeof(*slave_mod), GFP_KERNEL);
+	if (!slave_mod)
+		return -ENOMEM;
+
+	mod = &slave_mod->base;
+
+	tilcdc_module_init(mod, "slave", &slave_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;
+	}
+
+	slave_mod->i2c = of_find_i2c_adapter_by_node(i2c_node);
+	if (!slave_mod->i2c) {
+		dev_err(&pdev->dev, "could not get i2c\n");
+		goto fail;
+	}
+
+	of_node_put(i2c_node);
+
+	return 0;
+
+fail:
+	slave_destroy(mod);
+	return ret;
+}
+
+static int slave_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static struct of_device_id slave_of_match[] = {
+		{ .compatible = "tilcdc,slave", },
+		{ },
+};
+MODULE_DEVICE_TABLE(of, slave_of_match);
+
+struct platform_driver slave_driver = {
+	.probe = slave_probe,
+	.remove = slave_remove,
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = "slave",
+		.of_match_table = slave_of_match,
+	},
+};
+
+int __init tilcdc_slave_init(void)
+{
+	return platform_driver_register(&slave_driver);
+}
+
+void __exit tilcdc_slave_fini(void)
+{
+	platform_driver_unregister(&slave_driver);
+}
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave.h b/drivers/gpu/drm/tilcdc/tilcdc_slave.h
new file mode 100644
index 0000000..2f85048
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_slave.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 __TILCDC_SLAVE_H__
+#define __TILCDC_SLAVE_H__
+
+/* sub-module for i2c slave encoder output */
+
+int tilcdc_slave_init(void);
+void tilcdc_slave_fini(void);
+
+#endif /* __TILCDC_SLAVE_H__ */
-- 
1.8.1


^ permalink raw reply related	[flat|nested] 66+ messages in thread

* [PATCH 3/4] drm/tilcdc: add encoder slave
@ 2013-01-22 22:36   ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-22 22:36 UTC (permalink / raw)
  To: linux-arm-kernel

Add output panel driver for i2c encoder slaves.

Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 drivers/gpu/drm/tilcdc/Kconfig        |  12 ++
 drivers/gpu/drm/tilcdc/Makefile       |   1 +
 drivers/gpu/drm/tilcdc/tilcdc_drv.c   |   5 +-
 drivers/gpu/drm/tilcdc/tilcdc_slave.c | 380 ++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_slave.h |  26 +++
 5 files changed, 423 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.h

diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig
index ee9b592..99beca2 100644
--- a/drivers/gpu/drm/tilcdc/Kconfig
+++ b/drivers/gpu/drm/tilcdc/Kconfig
@@ -8,3 +8,15 @@ config DRM_TILCDC
 	  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.
+
+menu "I2C encoder or helper chips"
+	depends on DRM && DRM_KMS_HELPER && I2C
+
+config DRM_I2C_NXP_TDA998X
+	tristate "NXP Semiconductors TDA998X HDMI encoder"
+	default m if DRM_TILCDC
+	help
+	  Support for NXP Semiconductors TDA998X HDMI encoders.
+
+endmenu
+
diff --git a/drivers/gpu/drm/tilcdc/Makefile b/drivers/gpu/drm/tilcdc/Makefile
index 1359cc2..aa9097e 100644
--- a/drivers/gpu/drm/tilcdc/Makefile
+++ b/drivers/gpu/drm/tilcdc/Makefile
@@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm -Werror
 tilcdc-y := \
 	tilcdc_crtc.o \
 	tilcdc_tfp410.o \
+	tilcdc_slave.o \
 	tilcdc_drv.o
 
 obj-$(CONFIG_DRM_TILCDC)	+= tilcdc.o
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
index cf1fddc..ca76dbe 100644
--- a/drivers/gpu/drm/tilcdc/tilcdc_drv.c
+++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
@@ -20,6 +20,7 @@
 #include "tilcdc_drv.h"
 #include "tilcdc_regs.h"
 #include "tilcdc_tfp410.h"
+#include "tilcdc_slave.h"
 
 #include "drm_fb_helper.h"
 
@@ -587,6 +588,7 @@ static int __init tilcdc_drm_init(void)
 {
 	DBG("init");
 	tilcdc_tfp410_init();
+	tilcdc_slave_init();
 	return platform_driver_register(&tilcdc_platform_driver);
 }
 
@@ -594,10 +596,11 @@ static void __exit tilcdc_drm_fini(void)
 {
 	DBG("fini");
 	tilcdc_tfp410_fini();
+	tilcdc_slave_fini();
 	platform_driver_unregister(&tilcdc_platform_driver);
 }
 
-module_init(tilcdc_drm_init);
+late_initcall(tilcdc_drm_init);
 module_exit(tilcdc_drm_fini);
 
 MODULE_AUTHOR("Rob Clark <robdclark at gmail.com");
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave.c b/drivers/gpu/drm/tilcdc/tilcdc_slave.c
new file mode 100644
index 0000000..b6f3e63
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_slave.c
@@ -0,0 +1,380 @@
+/*
+ * 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/pinctrl/pinmux.h>
+#include <linux/pinctrl/consumer.h>
+#include <drm/drm_encoder_slave.h>
+
+#include "tilcdc_drv.h"
+
+struct slave_module {
+	struct tilcdc_module base;
+	struct i2c_adapter *i2c;
+};
+#define to_slave_module(x) container_of(x, struct slave_module, base)
+
+static const struct tilcdc_panel_info slave_info = {
+		.min_bpp                = 16,
+		.max_bpp                = 16,
+		.bpp                    = 16,
+		.ac_bias                = 255,
+		.ac_bias_intrpt         = 0,
+		.dma_burst_sz           = 16,
+		.fdd                    = 0x80,
+		.tft_alt_mode           = 0,
+		.stn_565_mode           = 0,
+		.mono_8bit_mode         = 0,
+		.sync_edge              = 0,
+		.sync_ctrl              = 1,
+		.raster_order           = 0,
+};
+
+
+/*
+ * Encoder:
+ */
+
+struct slave_encoder {
+	struct drm_encoder_slave base;
+	struct slave_module *mod;
+};
+#define to_slave_encoder(x) container_of(to_encoder_slave(x), struct slave_encoder, base)
+
+static inline struct drm_encoder_slave_funcs *
+get_slave_funcs(struct drm_encoder *enc)
+{
+	return to_encoder_slave(enc)->slave_funcs;
+}
+
+static void slave_encoder_destroy(struct drm_encoder *encoder)
+{
+	struct slave_encoder *slave_encoder = to_slave_encoder(encoder);
+	if (get_slave_funcs(encoder))
+		get_slave_funcs(encoder)->destroy(encoder);
+	drm_encoder_cleanup(encoder);
+	kfree(slave_encoder);
+}
+
+static void slave_encoder_prepare(struct drm_encoder *encoder)
+{
+	drm_i2c_encoder_prepare(encoder);
+	tilcdc_crtc_set_panel_info(encoder->crtc, &slave_info);
+}
+
+static const struct drm_encoder_funcs slave_encoder_funcs = {
+		.destroy        = slave_encoder_destroy,
+};
+
+static const struct drm_encoder_helper_funcs slave_encoder_helper_funcs = {
+		.dpms           = drm_i2c_encoder_dpms,
+		.mode_fixup     = drm_i2c_encoder_mode_fixup,
+		.prepare        = slave_encoder_prepare,
+		.commit         = drm_i2c_encoder_commit,
+		.mode_set       = drm_i2c_encoder_mode_set,
+		.save           = drm_i2c_encoder_save,
+		.restore        = drm_i2c_encoder_restore,
+};
+
+static const struct i2c_board_info info = {
+		I2C_BOARD_INFO("tda998x", 0x70)
+};
+
+static struct drm_encoder *slave_encoder_create(struct drm_device *dev,
+		struct slave_module *mod)
+{
+	struct slave_encoder *slave_encoder;
+	struct drm_encoder *encoder;
+	int ret;
+
+	slave_encoder = kzalloc(sizeof(*slave_encoder), GFP_KERNEL);
+	if (!slave_encoder) {
+		dev_err(dev->dev, "allocation failed\n");
+		return NULL;
+	}
+
+	slave_encoder->mod = mod;
+
+	encoder = &slave_encoder->base.base;
+	encoder->possible_crtcs = 1;
+
+	ret = drm_encoder_init(dev, encoder, &slave_encoder_funcs,
+			DRM_MODE_ENCODER_LVDS);
+	if (ret)
+		goto fail;
+
+	drm_encoder_helper_add(encoder, &slave_encoder_helper_funcs);
+
+	ret = drm_i2c_encoder_init(dev, to_encoder_slave(encoder), mod->i2c, &info);
+	if (ret)
+		goto fail;
+
+	return encoder;
+
+fail:
+	slave_encoder_destroy(encoder);
+	return NULL;
+}
+
+/*
+ * Connector:
+ */
+
+struct slave_connector {
+	struct drm_connector base;
+
+	struct drm_encoder *encoder;  /* our connected encoder */
+	struct slave_module *mod;
+};
+#define to_slave_connector(x) container_of(x, struct slave_connector, base)
+
+static void slave_connector_destroy(struct drm_connector *connector)
+{
+	struct slave_connector *slave_connector = to_slave_connector(connector);
+	drm_connector_cleanup(connector);
+	kfree(slave_connector);
+}
+
+static enum drm_connector_status slave_connector_detect(
+		struct drm_connector *connector,
+		bool force)
+{
+	struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
+	return get_slave_funcs(encoder)->detect(encoder, connector);
+}
+
+static int slave_connector_get_modes(struct drm_connector *connector)
+{
+	struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
+	return get_slave_funcs(encoder)->get_modes(encoder, connector);
+}
+
+static int slave_connector_mode_valid(struct drm_connector *connector,
+		  struct drm_display_mode *mode)
+{
+	struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
+	struct tilcdc_drm_private *priv = connector->dev->dev_private;
+	int ret;
+
+	ret = tilcdc_crtc_mode_valid(priv->crtc, mode);
+	if (ret != MODE_OK)
+		return ret;
+
+	return get_slave_funcs(encoder)->mode_valid(encoder, mode);
+}
+
+static struct drm_encoder *slave_connector_best_encoder(
+		struct drm_connector *connector)
+{
+	struct slave_connector *slave_connector = to_slave_connector(connector);
+	return slave_connector->encoder;
+}
+
+static int slave_connector_set_property(struct drm_connector *connector,
+		struct drm_property *property, uint64_t value)
+{
+	struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
+	return get_slave_funcs(encoder)->set_property(encoder,
+			connector, property, value);
+}
+
+static const struct drm_connector_funcs slave_connector_funcs = {
+	.destroy            = slave_connector_destroy,
+	.dpms               = drm_helper_connector_dpms,
+	.detect             = slave_connector_detect,
+	.fill_modes         = drm_helper_probe_single_connector_modes,
+	.set_property       = slave_connector_set_property,
+};
+
+static const struct drm_connector_helper_funcs slave_connector_helper_funcs = {
+	.get_modes          = slave_connector_get_modes,
+	.mode_valid         = slave_connector_mode_valid,
+	.best_encoder       = slave_connector_best_encoder,
+};
+
+static struct drm_connector *slave_connector_create(struct drm_device *dev,
+		struct slave_module *mod, struct drm_encoder *encoder)
+{
+	struct slave_connector *slave_connector;
+	struct drm_connector *connector;
+	int ret;
+
+	slave_connector = kzalloc(sizeof(*slave_connector), GFP_KERNEL);
+	if (!slave_connector) {
+		dev_err(dev->dev, "allocation failed\n");
+		return NULL;
+	}
+
+	slave_connector->encoder = encoder;
+	slave_connector->mod = mod;
+
+	connector = &slave_connector->base;
+
+	drm_connector_init(dev, connector, &slave_connector_funcs,
+			DRM_MODE_CONNECTOR_HDMIA);
+	drm_connector_helper_add(connector, &slave_connector_helper_funcs);
+
+	connector->polled = DRM_CONNECTOR_POLL_CONNECT |
+			DRM_CONNECTOR_POLL_DISCONNECT;
+
+	connector->interlace_allowed = 0;
+	connector->doublescan_allowed = 0;
+
+	get_slave_funcs(encoder)->create_resources(encoder, connector);
+
+	ret = drm_mode_connector_attach_encoder(connector, encoder);
+	if (ret)
+		goto fail;
+
+	drm_sysfs_connector_add(connector);
+
+	return connector;
+
+fail:
+	slave_connector_destroy(connector);
+	return NULL;
+}
+
+/*
+ * Module:
+ */
+
+static int slave_modeset_init(struct tilcdc_module *mod, struct drm_device *dev)
+{
+	struct slave_module *slave_mod = to_slave_module(mod);
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	struct drm_encoder *encoder;
+	struct drm_connector *connector;
+
+	encoder = slave_encoder_create(dev, slave_mod);
+	if (!encoder)
+		return -ENOMEM;
+
+	connector = slave_connector_create(dev, slave_mod, encoder);
+	if (!connector)
+		return -ENOMEM;
+
+	priv->encoders[priv->num_encoders++] = encoder;
+	priv->connectors[priv->num_connectors++] = connector;
+
+	return 0;
+}
+
+static void slave_destroy(struct tilcdc_module *mod)
+{
+	struct slave_module *slave_mod = to_slave_module(mod);
+
+	tilcdc_module_cleanup(mod);
+	kfree(slave_mod);
+}
+
+static const struct tilcdc_module_ops slave_module_ops = {
+		.modeset_init = slave_modeset_init,
+		.destroy = slave_destroy,
+};
+
+/*
+ * Device:
+ */
+
+static struct of_device_id slave_of_match[];
+
+static int slave_probe(struct platform_device *pdev)
+{
+	struct device_node *node = pdev->dev.of_node;
+	struct device_node *i2c_node;
+	struct slave_module *slave_mod;
+	struct tilcdc_module *mod;
+	struct pinctrl *pinctrl;
+	uint32_t i2c_phandle;
+	int ret = -EINVAL;
+
+	/* bail out early if no DT data: */
+	if (!node) {
+		dev_err(&pdev->dev, "device-tree data is missing\n");
+		return -ENXIO;
+	}
+
+	slave_mod = kzalloc(sizeof(*slave_mod), GFP_KERNEL);
+	if (!slave_mod)
+		return -ENOMEM;
+
+	mod = &slave_mod->base;
+
+	tilcdc_module_init(mod, "slave", &slave_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;
+	}
+
+	slave_mod->i2c = of_find_i2c_adapter_by_node(i2c_node);
+	if (!slave_mod->i2c) {
+		dev_err(&pdev->dev, "could not get i2c\n");
+		goto fail;
+	}
+
+	of_node_put(i2c_node);
+
+	return 0;
+
+fail:
+	slave_destroy(mod);
+	return ret;
+}
+
+static int slave_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static struct of_device_id slave_of_match[] = {
+		{ .compatible = "tilcdc,slave", },
+		{ },
+};
+MODULE_DEVICE_TABLE(of, slave_of_match);
+
+struct platform_driver slave_driver = {
+	.probe = slave_probe,
+	.remove = slave_remove,
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = "slave",
+		.of_match_table = slave_of_match,
+	},
+};
+
+int __init tilcdc_slave_init(void)
+{
+	return platform_driver_register(&slave_driver);
+}
+
+void __exit tilcdc_slave_fini(void)
+{
+	platform_driver_unregister(&slave_driver);
+}
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave.h b/drivers/gpu/drm/tilcdc/tilcdc_slave.h
new file mode 100644
index 0000000..2f85048
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_slave.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 __TILCDC_SLAVE_H__
+#define __TILCDC_SLAVE_H__
+
+/* sub-module for i2c slave encoder output */
+
+int tilcdc_slave_init(void);
+void tilcdc_slave_fini(void);
+
+#endif /* __TILCDC_SLAVE_H__ */
-- 
1.8.1

^ permalink raw reply related	[flat|nested] 66+ messages in thread

* [PATCH 4/4] drm/tilcdc: add support for LCD panels (v4)
  2013-01-22 22:36 ` Rob Clark
@ 2013-01-22 22:36   ` Rob Clark
  -1 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-22 22:36 UTC (permalink / raw)
  To: dri-devel; +Cc: patches, linux-omap, linux-arm-kernel, Rob Clark

Add an output panel driver for LCD panels.  Tested with LCD3 cape on
beaglebone.

v1: original
v2: s/of_find_node_by_name()/of_get_child_by_name()/ from Pantelis
    Antoniou
v3: add backlight support
v4: rebase to latest of video timing helpers

Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 drivers/gpu/drm/tilcdc/Kconfig        |   3 +
 drivers/gpu/drm/tilcdc/Makefile       |   1 +
 drivers/gpu/drm/tilcdc/tilcdc_drv.c   |   3 +
 drivers/gpu/drm/tilcdc/tilcdc_panel.c | 443 ++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_panel.h |  26 ++
 5 files changed, 476 insertions(+)
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_panel.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_panel.h

diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig
index 99beca2..f468b2b 100644
--- a/drivers/gpu/drm/tilcdc/Kconfig
+++ b/drivers/gpu/drm/tilcdc/Kconfig
@@ -4,6 +4,9 @@ config DRM_TILCDC
 	select DRM_KMS_HELPER
 	select DRM_KMS_CMA_HELPER
 	select DRM_GEM_CMA_HELPER
+	select OF_VIDEOMODE
+	select OF_DISPLAY_TIMING
+	select BACKLIGHT_CLASS_DEVICE
 	help
 	  Choose this option if you have an TI SoC with LCDC display
 	  controller, for example AM33xx in beagle-bone, DA8xx, or
diff --git a/drivers/gpu/drm/tilcdc/Makefile b/drivers/gpu/drm/tilcdc/Makefile
index aa9097e..deda656 100644
--- a/drivers/gpu/drm/tilcdc/Makefile
+++ b/drivers/gpu/drm/tilcdc/Makefile
@@ -4,6 +4,7 @@ tilcdc-y := \
 	tilcdc_crtc.o \
 	tilcdc_tfp410.o \
 	tilcdc_slave.o \
+	tilcdc_panel.o \
 	tilcdc_drv.o
 
 obj-$(CONFIG_DRM_TILCDC)	+= tilcdc.o
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
index ca76dbe..d10858c 100644
--- a/drivers/gpu/drm/tilcdc/tilcdc_drv.c
+++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
@@ -21,6 +21,7 @@
 #include "tilcdc_regs.h"
 #include "tilcdc_tfp410.h"
 #include "tilcdc_slave.h"
+#include "tilcdc_panel.h"
 
 #include "drm_fb_helper.h"
 
@@ -589,6 +590,7 @@ static int __init tilcdc_drm_init(void)
 	DBG("init");
 	tilcdc_tfp410_init();
 	tilcdc_slave_init();
+	tilcdc_panel_init();
 	return platform_driver_register(&tilcdc_platform_driver);
 }
 
@@ -597,6 +599,7 @@ static void __exit tilcdc_drm_fini(void)
 	DBG("fini");
 	tilcdc_tfp410_fini();
 	tilcdc_slave_fini();
+	tilcdc_panel_fini();
 	platform_driver_unregister(&tilcdc_platform_driver);
 }
 
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_panel.c b/drivers/gpu/drm/tilcdc/tilcdc_panel.c
new file mode 100644
index 0000000..a3c7fe93
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_panel.c
@@ -0,0 +1,443 @@
+/*
+ * 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/pinctrl/pinmux.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/backlight.h>
+#include <video/display_timing.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+
+#include "tilcdc_drv.h"
+
+struct panel_module {
+	struct tilcdc_module base;
+	struct tilcdc_panel_info *info;
+	struct display_timings *timings;
+	struct backlight_device *backlight;
+};
+#define to_panel_module(x) container_of(x, struct panel_module, base)
+
+
+/*
+ * Encoder:
+ */
+
+struct panel_encoder {
+	struct drm_encoder base;
+	struct panel_module *mod;
+};
+#define to_panel_encoder(x) container_of(x, struct panel_encoder, base)
+
+
+static void panel_encoder_destroy(struct drm_encoder *encoder)
+{
+	struct panel_encoder *panel_encoder = to_panel_encoder(encoder);
+	drm_encoder_cleanup(encoder);
+	kfree(panel_encoder);
+}
+
+static void panel_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+	struct panel_encoder *panel_encoder = to_panel_encoder(encoder);
+	struct backlight_device *backlight = panel_encoder->mod->backlight;
+
+	if (!backlight)
+		return;
+
+	backlight->props.power = mode == DRM_MODE_DPMS_ON
+				     ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
+	backlight_update_status(backlight);
+}
+
+static bool panel_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 panel_encoder_prepare(struct drm_encoder *encoder)
+{
+	struct panel_encoder *panel_encoder = to_panel_encoder(encoder);
+	panel_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
+	tilcdc_crtc_set_panel_info(encoder->crtc, panel_encoder->mod->info);
+}
+
+static void panel_encoder_commit(struct drm_encoder *encoder)
+{
+	panel_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
+}
+
+static void panel_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 panel_encoder_funcs = {
+		.destroy        = panel_encoder_destroy,
+};
+
+static const struct drm_encoder_helper_funcs panel_encoder_helper_funcs = {
+		.dpms           = panel_encoder_dpms,
+		.mode_fixup     = panel_encoder_mode_fixup,
+		.prepare        = panel_encoder_prepare,
+		.commit         = panel_encoder_commit,
+		.mode_set       = panel_encoder_mode_set,
+};
+
+static struct drm_encoder *panel_encoder_create(struct drm_device *dev,
+		struct panel_module *mod)
+{
+	struct panel_encoder *panel_encoder;
+	struct drm_encoder *encoder;
+	int ret;
+
+	panel_encoder = kzalloc(sizeof(*panel_encoder), GFP_KERNEL);
+	if (!panel_encoder) {
+		dev_err(dev->dev, "allocation failed\n");
+		return NULL;
+	}
+
+	panel_encoder->mod = mod;
+
+	encoder = &panel_encoder->base;
+	encoder->possible_crtcs = 1;
+
+	ret = drm_encoder_init(dev, encoder, &panel_encoder_funcs,
+			DRM_MODE_ENCODER_LVDS);
+	if (ret < 0)
+		goto fail;
+
+	drm_encoder_helper_add(encoder, &panel_encoder_helper_funcs);
+
+	return encoder;
+
+fail:
+	panel_encoder_destroy(encoder);
+	return NULL;
+}
+
+/*
+ * Connector:
+ */
+
+struct panel_connector {
+	struct drm_connector base;
+
+	struct drm_encoder *encoder;  /* our connected encoder */
+	struct panel_module *mod;
+};
+#define to_panel_connector(x) container_of(x, struct panel_connector, base)
+
+
+static void panel_connector_destroy(struct drm_connector *connector)
+{
+	struct panel_connector *panel_connector = to_panel_connector(connector);
+	drm_connector_cleanup(connector);
+	kfree(panel_connector);
+}
+
+static enum drm_connector_status panel_connector_detect(
+		struct drm_connector *connector,
+		bool force)
+{
+	return connector_status_connected;
+}
+
+static int panel_connector_get_modes(struct drm_connector *connector)
+{
+	struct drm_device *dev = connector->dev;
+	struct panel_connector *panel_connector = to_panel_connector(connector);
+	struct display_timings *timings = panel_connector->mod->timings;
+	int i;
+
+	for (i = 0; i < timings->num_timings; i++) {
+		struct drm_display_mode *mode = drm_mode_create(dev);
+		struct videomode vm;
+
+		if (videomode_from_timing(timings, &vm, i))
+			break;
+
+		drm_display_mode_from_videomode(&vm, mode);
+
+		mode->type = DRM_MODE_TYPE_DRIVER;
+
+		if (timings->native_mode == i)
+			mode->type |= DRM_MODE_TYPE_PREFERRED;
+
+		drm_mode_set_name(mode);
+		drm_mode_probed_add(connector, mode);
+	}
+
+	return i;
+}
+
+static int panel_connector_mode_valid(struct drm_connector *connector,
+		  struct drm_display_mode *mode)
+{
+	struct tilcdc_drm_private *priv = connector->dev->dev_private;
+	/* our only constraints are what the crtc can generate: */
+	return tilcdc_crtc_mode_valid(priv->crtc, mode);
+}
+
+static struct drm_encoder *panel_connector_best_encoder(
+		struct drm_connector *connector)
+{
+	struct panel_connector *panel_connector = to_panel_connector(connector);
+	return panel_connector->encoder;
+}
+
+static const struct drm_connector_funcs panel_connector_funcs = {
+	.destroy            = panel_connector_destroy,
+	.dpms               = drm_helper_connector_dpms,
+	.detect             = panel_connector_detect,
+	.fill_modes         = drm_helper_probe_single_connector_modes,
+};
+
+static const struct drm_connector_helper_funcs panel_connector_helper_funcs = {
+	.get_modes          = panel_connector_get_modes,
+	.mode_valid         = panel_connector_mode_valid,
+	.best_encoder       = panel_connector_best_encoder,
+};
+
+static struct drm_connector *panel_connector_create(struct drm_device *dev,
+		struct panel_module *mod, struct drm_encoder *encoder)
+{
+	struct panel_connector *panel_connector;
+	struct drm_connector *connector;
+	int ret;
+
+	panel_connector = kzalloc(sizeof(*panel_connector), GFP_KERNEL);
+	if (!panel_connector) {
+		dev_err(dev->dev, "allocation failed\n");
+		return NULL;
+	}
+
+	panel_connector->encoder = encoder;
+	panel_connector->mod = mod;
+
+	connector = &panel_connector->base;
+
+	drm_connector_init(dev, connector, &panel_connector_funcs,
+			DRM_MODE_CONNECTOR_LVDS);
+	drm_connector_helper_add(connector, &panel_connector_helper_funcs);
+
+	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:
+	panel_connector_destroy(connector);
+	return NULL;
+}
+
+/*
+ * Module:
+ */
+
+static int panel_modeset_init(struct tilcdc_module *mod, struct drm_device *dev)
+{
+	struct panel_module *panel_mod = to_panel_module(mod);
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	struct drm_encoder *encoder;
+	struct drm_connector *connector;
+
+	encoder = panel_encoder_create(dev, panel_mod);
+	if (!encoder)
+		return -ENOMEM;
+
+	connector = panel_connector_create(dev, panel_mod, encoder);
+	if (!connector)
+		return -ENOMEM;
+
+	priv->encoders[priv->num_encoders++] = encoder;
+	priv->connectors[priv->num_connectors++] = connector;
+
+	return 0;
+}
+
+static void panel_destroy(struct tilcdc_module *mod)
+{
+	struct panel_module *panel_mod = to_panel_module(mod);
+
+	if (panel_mod->timings) {
+		display_timings_release(panel_mod->timings);
+		kfree(panel_mod->timings);
+	}
+
+	tilcdc_module_cleanup(mod);
+	kfree(panel_mod->info);
+	kfree(panel_mod);
+}
+
+static const struct tilcdc_module_ops panel_module_ops = {
+		.modeset_init = panel_modeset_init,
+		.destroy = panel_destroy,
+};
+
+/*
+ * Device:
+ */
+
+/* maybe move this somewhere common if it is needed by other outputs? */
+static struct tilcdc_panel_info * of_get_panel_info(struct device_node *np)
+{
+	struct device_node *info_np;
+	struct tilcdc_panel_info *info;
+	int ret = 0;
+
+	if (!np) {
+		pr_err("%s: no devicenode given\n", __func__);
+		return NULL;
+	}
+
+	info_np = of_get_child_by_name(np, "panel-info");
+	if (!info_np) {
+		pr_err("%s: could not find panel-info node\n", __func__);
+		return NULL;
+	}
+
+	info = kzalloc(sizeof(*info), GFP_KERNEL);
+	if (!info) {
+		pr_err("%s: allocation failed\n", __func__);
+		return NULL;
+	}
+
+	ret |= of_property_read_u32(info_np, "ac-bias", &info->ac_bias);
+	ret |= of_property_read_u32(info_np, "ac-bias-intrpt", &info->ac_bias_intrpt);
+	ret |= of_property_read_u32(info_np, "dma-burst-sz", &info->dma_burst_sz);
+	ret |= of_property_read_u32(info_np, "bpp", &info->bpp);
+	ret |= of_property_read_u32(info_np, "fdd", &info->fdd);
+	ret |= of_property_read_u32(info_np, "sync-edge", &info->sync_edge);
+	ret |= of_property_read_u32(info_np, "sync-ctrl", &info->sync_ctrl);
+	ret |= of_property_read_u32(info_np, "raster-order", &info->raster_order);
+	ret |= of_property_read_u32(info_np, "fifo-th", &info->fifo_th);
+
+	/* optional: */
+	info->tft_alt_mode      = of_property_read_bool(info_np, "tft-alt-mode");
+	info->stn_565_mode      = of_property_read_bool(info_np, "stn-565-mode");
+	info->mono_8bit_mode    = of_property_read_bool(info_np, "mono-8bit-mode");
+	info->invert_pxl_clk    = of_property_read_bool(info_np, "invert-pxl-clk");
+
+	if (of_property_read_u32(info_np, "max-bpp", &info->max_bpp))
+		info->max_bpp = info->bpp;
+	if (of_property_read_u32(info_np, "min-bpp", &info->min_bpp))
+		info->min_bpp = info->bpp;
+
+	if (ret) {
+		pr_err("%s: error reading panel-info properties\n", __func__);
+		kfree(info);
+		return NULL;
+	}
+
+	return info;
+}
+
+static struct of_device_id panel_of_match[];
+
+static int panel_probe(struct platform_device *pdev)
+{
+	struct device_node *node = pdev->dev.of_node;
+	struct panel_module *panel_mod;
+	struct tilcdc_module *mod;
+	struct pinctrl *pinctrl;
+	int ret = -EINVAL;
+
+
+	/* bail out early if no DT data: */
+	if (!node) {
+		dev_err(&pdev->dev, "device-tree data is missing\n");
+		return -ENXIO;
+	}
+
+	panel_mod = kzalloc(sizeof(*panel_mod), GFP_KERNEL);
+	if (!panel_mod)
+		return -ENOMEM;
+
+	mod = &panel_mod->base;
+
+	tilcdc_module_init(mod, "panel", &panel_module_ops);
+
+	pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
+	if (IS_ERR(pinctrl))
+		dev_warn(&pdev->dev, "pins are not configured\n");
+
+
+	panel_mod->timings = of_get_display_timings(node);
+	if (!panel_mod->timings) {
+		dev_err(&pdev->dev, "could not get panel timings\n");
+		goto fail;
+	}
+
+	panel_mod->info = of_get_panel_info(node);
+	if (!panel_mod->info) {
+		dev_err(&pdev->dev, "could not get panel info\n");
+		goto fail;
+	}
+
+	panel_mod->backlight = of_find_backlight_by_node(node);
+	if (panel_mod->backlight)
+		dev_info(&pdev->dev, "found backlight\n");
+
+	return 0;
+
+fail:
+	panel_destroy(mod);
+	return ret;
+}
+
+static int panel_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static struct of_device_id panel_of_match[] = {
+		{ .compatible = "tilcdc,panel", },
+		{ },
+};
+MODULE_DEVICE_TABLE(of, panel_of_match);
+
+struct platform_driver panel_driver = {
+	.probe = panel_probe,
+	.remove = panel_remove,
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = "panel",
+		.of_match_table = panel_of_match,
+	},
+};
+
+int __init tilcdc_panel_init(void)
+{
+	return platform_driver_register(&panel_driver);
+}
+
+void __exit tilcdc_panel_fini(void)
+{
+	platform_driver_unregister(&panel_driver);
+}
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_panel.h b/drivers/gpu/drm/tilcdc/tilcdc_panel.h
new file mode 100644
index 0000000..7db40aa
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_panel.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 __TILCDC_PANEL_H__
+#define __TILCDC_PANEL_H__
+
+/* sub-module for generic lcd panel output */
+
+int tilcdc_panel_init(void);
+void tilcdc_panel_fini(void);
+
+#endif /* __TILCDC_PANEL_H__ */
-- 
1.8.1


^ permalink raw reply related	[flat|nested] 66+ messages in thread

* [PATCH 4/4] drm/tilcdc: add support for LCD panels (v4)
@ 2013-01-22 22:36   ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-22 22:36 UTC (permalink / raw)
  To: linux-arm-kernel

Add an output panel driver for LCD panels.  Tested with LCD3 cape on
beaglebone.

v1: original
v2: s/of_find_node_by_name()/of_get_child_by_name()/ from Pantelis
    Antoniou
v3: add backlight support
v4: rebase to latest of video timing helpers

Signed-off-by: Rob Clark <robdclark@gmail.com>
---
 drivers/gpu/drm/tilcdc/Kconfig        |   3 +
 drivers/gpu/drm/tilcdc/Makefile       |   1 +
 drivers/gpu/drm/tilcdc/tilcdc_drv.c   |   3 +
 drivers/gpu/drm/tilcdc/tilcdc_panel.c | 443 ++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/tilcdc/tilcdc_panel.h |  26 ++
 5 files changed, 476 insertions(+)
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_panel.c
 create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_panel.h

diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig
index 99beca2..f468b2b 100644
--- a/drivers/gpu/drm/tilcdc/Kconfig
+++ b/drivers/gpu/drm/tilcdc/Kconfig
@@ -4,6 +4,9 @@ config DRM_TILCDC
 	select DRM_KMS_HELPER
 	select DRM_KMS_CMA_HELPER
 	select DRM_GEM_CMA_HELPER
+	select OF_VIDEOMODE
+	select OF_DISPLAY_TIMING
+	select BACKLIGHT_CLASS_DEVICE
 	help
 	  Choose this option if you have an TI SoC with LCDC display
 	  controller, for example AM33xx in beagle-bone, DA8xx, or
diff --git a/drivers/gpu/drm/tilcdc/Makefile b/drivers/gpu/drm/tilcdc/Makefile
index aa9097e..deda656 100644
--- a/drivers/gpu/drm/tilcdc/Makefile
+++ b/drivers/gpu/drm/tilcdc/Makefile
@@ -4,6 +4,7 @@ tilcdc-y := \
 	tilcdc_crtc.o \
 	tilcdc_tfp410.o \
 	tilcdc_slave.o \
+	tilcdc_panel.o \
 	tilcdc_drv.o
 
 obj-$(CONFIG_DRM_TILCDC)	+= tilcdc.o
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
index ca76dbe..d10858c 100644
--- a/drivers/gpu/drm/tilcdc/tilcdc_drv.c
+++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
@@ -21,6 +21,7 @@
 #include "tilcdc_regs.h"
 #include "tilcdc_tfp410.h"
 #include "tilcdc_slave.h"
+#include "tilcdc_panel.h"
 
 #include "drm_fb_helper.h"
 
@@ -589,6 +590,7 @@ static int __init tilcdc_drm_init(void)
 	DBG("init");
 	tilcdc_tfp410_init();
 	tilcdc_slave_init();
+	tilcdc_panel_init();
 	return platform_driver_register(&tilcdc_platform_driver);
 }
 
@@ -597,6 +599,7 @@ static void __exit tilcdc_drm_fini(void)
 	DBG("fini");
 	tilcdc_tfp410_fini();
 	tilcdc_slave_fini();
+	tilcdc_panel_fini();
 	platform_driver_unregister(&tilcdc_platform_driver);
 }
 
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_panel.c b/drivers/gpu/drm/tilcdc/tilcdc_panel.c
new file mode 100644
index 0000000..a3c7fe93
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_panel.c
@@ -0,0 +1,443 @@
+/*
+ * 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/pinctrl/pinmux.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/backlight.h>
+#include <video/display_timing.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+
+#include "tilcdc_drv.h"
+
+struct panel_module {
+	struct tilcdc_module base;
+	struct tilcdc_panel_info *info;
+	struct display_timings *timings;
+	struct backlight_device *backlight;
+};
+#define to_panel_module(x) container_of(x, struct panel_module, base)
+
+
+/*
+ * Encoder:
+ */
+
+struct panel_encoder {
+	struct drm_encoder base;
+	struct panel_module *mod;
+};
+#define to_panel_encoder(x) container_of(x, struct panel_encoder, base)
+
+
+static void panel_encoder_destroy(struct drm_encoder *encoder)
+{
+	struct panel_encoder *panel_encoder = to_panel_encoder(encoder);
+	drm_encoder_cleanup(encoder);
+	kfree(panel_encoder);
+}
+
+static void panel_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+	struct panel_encoder *panel_encoder = to_panel_encoder(encoder);
+	struct backlight_device *backlight = panel_encoder->mod->backlight;
+
+	if (!backlight)
+		return;
+
+	backlight->props.power = mode == DRM_MODE_DPMS_ON
+				     ? FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
+	backlight_update_status(backlight);
+}
+
+static bool panel_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 panel_encoder_prepare(struct drm_encoder *encoder)
+{
+	struct panel_encoder *panel_encoder = to_panel_encoder(encoder);
+	panel_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
+	tilcdc_crtc_set_panel_info(encoder->crtc, panel_encoder->mod->info);
+}
+
+static void panel_encoder_commit(struct drm_encoder *encoder)
+{
+	panel_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
+}
+
+static void panel_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 panel_encoder_funcs = {
+		.destroy        = panel_encoder_destroy,
+};
+
+static const struct drm_encoder_helper_funcs panel_encoder_helper_funcs = {
+		.dpms           = panel_encoder_dpms,
+		.mode_fixup     = panel_encoder_mode_fixup,
+		.prepare        = panel_encoder_prepare,
+		.commit         = panel_encoder_commit,
+		.mode_set       = panel_encoder_mode_set,
+};
+
+static struct drm_encoder *panel_encoder_create(struct drm_device *dev,
+		struct panel_module *mod)
+{
+	struct panel_encoder *panel_encoder;
+	struct drm_encoder *encoder;
+	int ret;
+
+	panel_encoder = kzalloc(sizeof(*panel_encoder), GFP_KERNEL);
+	if (!panel_encoder) {
+		dev_err(dev->dev, "allocation failed\n");
+		return NULL;
+	}
+
+	panel_encoder->mod = mod;
+
+	encoder = &panel_encoder->base;
+	encoder->possible_crtcs = 1;
+
+	ret = drm_encoder_init(dev, encoder, &panel_encoder_funcs,
+			DRM_MODE_ENCODER_LVDS);
+	if (ret < 0)
+		goto fail;
+
+	drm_encoder_helper_add(encoder, &panel_encoder_helper_funcs);
+
+	return encoder;
+
+fail:
+	panel_encoder_destroy(encoder);
+	return NULL;
+}
+
+/*
+ * Connector:
+ */
+
+struct panel_connector {
+	struct drm_connector base;
+
+	struct drm_encoder *encoder;  /* our connected encoder */
+	struct panel_module *mod;
+};
+#define to_panel_connector(x) container_of(x, struct panel_connector, base)
+
+
+static void panel_connector_destroy(struct drm_connector *connector)
+{
+	struct panel_connector *panel_connector = to_panel_connector(connector);
+	drm_connector_cleanup(connector);
+	kfree(panel_connector);
+}
+
+static enum drm_connector_status panel_connector_detect(
+		struct drm_connector *connector,
+		bool force)
+{
+	return connector_status_connected;
+}
+
+static int panel_connector_get_modes(struct drm_connector *connector)
+{
+	struct drm_device *dev = connector->dev;
+	struct panel_connector *panel_connector = to_panel_connector(connector);
+	struct display_timings *timings = panel_connector->mod->timings;
+	int i;
+
+	for (i = 0; i < timings->num_timings; i++) {
+		struct drm_display_mode *mode = drm_mode_create(dev);
+		struct videomode vm;
+
+		if (videomode_from_timing(timings, &vm, i))
+			break;
+
+		drm_display_mode_from_videomode(&vm, mode);
+
+		mode->type = DRM_MODE_TYPE_DRIVER;
+
+		if (timings->native_mode == i)
+			mode->type |= DRM_MODE_TYPE_PREFERRED;
+
+		drm_mode_set_name(mode);
+		drm_mode_probed_add(connector, mode);
+	}
+
+	return i;
+}
+
+static int panel_connector_mode_valid(struct drm_connector *connector,
+		  struct drm_display_mode *mode)
+{
+	struct tilcdc_drm_private *priv = connector->dev->dev_private;
+	/* our only constraints are what the crtc can generate: */
+	return tilcdc_crtc_mode_valid(priv->crtc, mode);
+}
+
+static struct drm_encoder *panel_connector_best_encoder(
+		struct drm_connector *connector)
+{
+	struct panel_connector *panel_connector = to_panel_connector(connector);
+	return panel_connector->encoder;
+}
+
+static const struct drm_connector_funcs panel_connector_funcs = {
+	.destroy            = panel_connector_destroy,
+	.dpms               = drm_helper_connector_dpms,
+	.detect             = panel_connector_detect,
+	.fill_modes         = drm_helper_probe_single_connector_modes,
+};
+
+static const struct drm_connector_helper_funcs panel_connector_helper_funcs = {
+	.get_modes          = panel_connector_get_modes,
+	.mode_valid         = panel_connector_mode_valid,
+	.best_encoder       = panel_connector_best_encoder,
+};
+
+static struct drm_connector *panel_connector_create(struct drm_device *dev,
+		struct panel_module *mod, struct drm_encoder *encoder)
+{
+	struct panel_connector *panel_connector;
+	struct drm_connector *connector;
+	int ret;
+
+	panel_connector = kzalloc(sizeof(*panel_connector), GFP_KERNEL);
+	if (!panel_connector) {
+		dev_err(dev->dev, "allocation failed\n");
+		return NULL;
+	}
+
+	panel_connector->encoder = encoder;
+	panel_connector->mod = mod;
+
+	connector = &panel_connector->base;
+
+	drm_connector_init(dev, connector, &panel_connector_funcs,
+			DRM_MODE_CONNECTOR_LVDS);
+	drm_connector_helper_add(connector, &panel_connector_helper_funcs);
+
+	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:
+	panel_connector_destroy(connector);
+	return NULL;
+}
+
+/*
+ * Module:
+ */
+
+static int panel_modeset_init(struct tilcdc_module *mod, struct drm_device *dev)
+{
+	struct panel_module *panel_mod = to_panel_module(mod);
+	struct tilcdc_drm_private *priv = dev->dev_private;
+	struct drm_encoder *encoder;
+	struct drm_connector *connector;
+
+	encoder = panel_encoder_create(dev, panel_mod);
+	if (!encoder)
+		return -ENOMEM;
+
+	connector = panel_connector_create(dev, panel_mod, encoder);
+	if (!connector)
+		return -ENOMEM;
+
+	priv->encoders[priv->num_encoders++] = encoder;
+	priv->connectors[priv->num_connectors++] = connector;
+
+	return 0;
+}
+
+static void panel_destroy(struct tilcdc_module *mod)
+{
+	struct panel_module *panel_mod = to_panel_module(mod);
+
+	if (panel_mod->timings) {
+		display_timings_release(panel_mod->timings);
+		kfree(panel_mod->timings);
+	}
+
+	tilcdc_module_cleanup(mod);
+	kfree(panel_mod->info);
+	kfree(panel_mod);
+}
+
+static const struct tilcdc_module_ops panel_module_ops = {
+		.modeset_init = panel_modeset_init,
+		.destroy = panel_destroy,
+};
+
+/*
+ * Device:
+ */
+
+/* maybe move this somewhere common if it is needed by other outputs? */
+static struct tilcdc_panel_info * of_get_panel_info(struct device_node *np)
+{
+	struct device_node *info_np;
+	struct tilcdc_panel_info *info;
+	int ret = 0;
+
+	if (!np) {
+		pr_err("%s: no devicenode given\n", __func__);
+		return NULL;
+	}
+
+	info_np = of_get_child_by_name(np, "panel-info");
+	if (!info_np) {
+		pr_err("%s: could not find panel-info node\n", __func__);
+		return NULL;
+	}
+
+	info = kzalloc(sizeof(*info), GFP_KERNEL);
+	if (!info) {
+		pr_err("%s: allocation failed\n", __func__);
+		return NULL;
+	}
+
+	ret |= of_property_read_u32(info_np, "ac-bias", &info->ac_bias);
+	ret |= of_property_read_u32(info_np, "ac-bias-intrpt", &info->ac_bias_intrpt);
+	ret |= of_property_read_u32(info_np, "dma-burst-sz", &info->dma_burst_sz);
+	ret |= of_property_read_u32(info_np, "bpp", &info->bpp);
+	ret |= of_property_read_u32(info_np, "fdd", &info->fdd);
+	ret |= of_property_read_u32(info_np, "sync-edge", &info->sync_edge);
+	ret |= of_property_read_u32(info_np, "sync-ctrl", &info->sync_ctrl);
+	ret |= of_property_read_u32(info_np, "raster-order", &info->raster_order);
+	ret |= of_property_read_u32(info_np, "fifo-th", &info->fifo_th);
+
+	/* optional: */
+	info->tft_alt_mode      = of_property_read_bool(info_np, "tft-alt-mode");
+	info->stn_565_mode      = of_property_read_bool(info_np, "stn-565-mode");
+	info->mono_8bit_mode    = of_property_read_bool(info_np, "mono-8bit-mode");
+	info->invert_pxl_clk    = of_property_read_bool(info_np, "invert-pxl-clk");
+
+	if (of_property_read_u32(info_np, "max-bpp", &info->max_bpp))
+		info->max_bpp = info->bpp;
+	if (of_property_read_u32(info_np, "min-bpp", &info->min_bpp))
+		info->min_bpp = info->bpp;
+
+	if (ret) {
+		pr_err("%s: error reading panel-info properties\n", __func__);
+		kfree(info);
+		return NULL;
+	}
+
+	return info;
+}
+
+static struct of_device_id panel_of_match[];
+
+static int panel_probe(struct platform_device *pdev)
+{
+	struct device_node *node = pdev->dev.of_node;
+	struct panel_module *panel_mod;
+	struct tilcdc_module *mod;
+	struct pinctrl *pinctrl;
+	int ret = -EINVAL;
+
+
+	/* bail out early if no DT data: */
+	if (!node) {
+		dev_err(&pdev->dev, "device-tree data is missing\n");
+		return -ENXIO;
+	}
+
+	panel_mod = kzalloc(sizeof(*panel_mod), GFP_KERNEL);
+	if (!panel_mod)
+		return -ENOMEM;
+
+	mod = &panel_mod->base;
+
+	tilcdc_module_init(mod, "panel", &panel_module_ops);
+
+	pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
+	if (IS_ERR(pinctrl))
+		dev_warn(&pdev->dev, "pins are not configured\n");
+
+
+	panel_mod->timings = of_get_display_timings(node);
+	if (!panel_mod->timings) {
+		dev_err(&pdev->dev, "could not get panel timings\n");
+		goto fail;
+	}
+
+	panel_mod->info = of_get_panel_info(node);
+	if (!panel_mod->info) {
+		dev_err(&pdev->dev, "could not get panel info\n");
+		goto fail;
+	}
+
+	panel_mod->backlight = of_find_backlight_by_node(node);
+	if (panel_mod->backlight)
+		dev_info(&pdev->dev, "found backlight\n");
+
+	return 0;
+
+fail:
+	panel_destroy(mod);
+	return ret;
+}
+
+static int panel_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static struct of_device_id panel_of_match[] = {
+		{ .compatible = "tilcdc,panel", },
+		{ },
+};
+MODULE_DEVICE_TABLE(of, panel_of_match);
+
+struct platform_driver panel_driver = {
+	.probe = panel_probe,
+	.remove = panel_remove,
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = "panel",
+		.of_match_table = panel_of_match,
+	},
+};
+
+int __init tilcdc_panel_init(void)
+{
+	return platform_driver_register(&panel_driver);
+}
+
+void __exit tilcdc_panel_fini(void)
+{
+	platform_driver_unregister(&panel_driver);
+}
diff --git a/drivers/gpu/drm/tilcdc/tilcdc_panel.h b/drivers/gpu/drm/tilcdc/tilcdc_panel.h
new file mode 100644
index 0000000..7db40aa
--- /dev/null
+++ b/drivers/gpu/drm/tilcdc/tilcdc_panel.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 __TILCDC_PANEL_H__
+#define __TILCDC_PANEL_H__
+
+/* sub-module for generic lcd panel output */
+
+int tilcdc_panel_init(void);
+void tilcdc_panel_fini(void);
+
+#endif /* __TILCDC_PANEL_H__ */
-- 
1.8.1

^ permalink raw reply related	[flat|nested] 66+ messages in thread

* Re: [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
  2013-01-22 22:36   ` Rob Clark
@ 2013-01-22 23:41     ` Daniel Vetter
  -1 siblings, 0 replies; 66+ messages in thread
From: Daniel Vetter @ 2013-01-22 23:41 UTC (permalink / raw)
  To: Rob Clark; +Cc: dri-devel, linux-omap, linux-arm-kernel, patches

On Tue, Jan 22, 2013 at 04:36:22PM -0600, 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.
> 
> v1: original
> v2: fix fb refcnting and few other cleanups
> v3: get +/- vsync/hsync from timings rather than panel-info, add
>     option DT max-bandwidth field so driver doesn't attempt to
>     pick a display mode with too high memory bandwidth, and other
>     small cleanups
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>

Ok, read through it, looks nice. No idea whether this should use the panel
stuff, but since no-one else does who cares. A few nits and questions
below. Was a nice reading to learn about kfifo and the runtime power
stuff.

Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>

> ---
>  drivers/gpu/drm/Kconfig                |   2 +
>  drivers/gpu/drm/Makefile               |   1 +
>  drivers/gpu/drm/tilcdc/Kconfig         |  10 +
>  drivers/gpu/drm/tilcdc/Makefile        |   8 +
>  drivers/gpu/drm/tilcdc/tilcdc_crtc.c   | 597 ++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_drv.c    | 605 +++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_drv.h    | 159 +++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_regs.h   | 154 +++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_tfp410.c | 423 +++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_tfp410.h |  26 ++
>  10 files changed, 1985 insertions(+)
>  create mode 100644 drivers/gpu/drm/tilcdc/Kconfig
>  create mode 100644 drivers/gpu/drm/tilcdc/Makefile
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_crtc.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.h
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_regs.h
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.h
> 

[cut]

> +struct tilcdc_crtc {
> +	struct drm_crtc base;
> +
> +	const struct tilcdc_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;
> +
> +	/* fb currently set to scanout 0/1: */
> +	struct drm_framebuffer *scanout[2];
> +
> +	/* for deferred fb unref's: */
> +	DECLARE_KFIFO_PTR(unref_fifo, struct drm_framebuffer *);
> +	struct work_struct work;
> +};
> +#define to_tilcdc_crtc(x) container_of(x, struct tilcdc_crtc, base)
> +
> +static void unref_worker(struct work_struct *work)
> +{
> +	struct tilcdc_crtc *tilcdc_crtc = container_of(work, struct tilcdc_crtc, work);
> +	struct drm_device *dev = tilcdc_crtc->base.dev;
> +	struct drm_framebuffer *fb;
> +
> +	mutex_lock(&dev->mode_config.mutex);
> +	while (kfifo_get(&tilcdc_crtc->unref_fifo, &fb))
> +		drm_framebuffer_unreference(fb);
> +	mutex_unlock(&dev->mode_config.mutex);

Hm, just learned about the kfifo api. It looks like the locking here still
works even with the new modeset locking, since kfifo explicitly allows
concurrent readers and writers without locking, as long as there's only on
of each kind. But maybe switch over to crtc->mutex to make things less
tricky.

Also, kfifo seems to have a new api which allows embedding of the kfifo
thing and needs to be used with kfifo_in/out.

[cut]

> +static void update_scanout(struct drm_crtc *crtc)
> +{
> +	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_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);
> +
> +	tilcdc_crtc->start = gem->paddr + fb->offsets[0] +
> +			(crtc->y * fb->pitches[0]) + (crtc->x * bpp/8);
> +
> +	tilcdc_crtc->end = tilcdc_crtc->start +
> +			(crtc->mode.vdisplay * fb->pitches[0]);
> +
> +	if (tilcdc_crtc->dpms == DRM_MODE_DPMS_ON) {
> +		/* already enabled, so just mark the frames that need
> +		 * updating and they will be updated on vblank:
> +		 */
> +		tilcdc_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, 0);
> +		set_scanout(crtc, 1);

At least on intel we disallow pageflips on disabled crtcs since
drm_vblank_get will fail. So dunno whether this is allowed on other
places, but in any case I don't see a drm_vblank_handle call, which means
we'll miss out on the pageflip completion event ...

> +	}
> +}
> +
> +static void start(struct drm_crtc *crtc)
> +{
> +	struct drm_device *dev = crtc->dev;
> +	struct tilcdc_drm_private *priv = dev->dev_private;
> +
> +	if (priv->rev == 2) {
> +		tilcdc_set(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET);
> +		msleep(1);
> +		tilcdc_clear(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET);
> +		msleep(1);
> +	}
> +
> +	tilcdc_set(dev, LCDC_DMA_CTRL_REG, LCDC_DUAL_FRAME_BUFFER_ENABLE);
> +	tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_PALETTE_LOAD_MODE(DATA_ONLY));
> +	tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
> +}
> +
> +static void stop(struct drm_crtc *crtc)
> +{
> +	struct drm_device *dev = crtc->dev;
> +
> +	tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
> +}
> +
> +static void tilcdc_crtc_destroy(struct drm_crtc *crtc)
> +{
> +	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> +
> +	WARN_ON(tilcdc_crtc->dpms == DRM_MODE_DPMS_ON);
> +
> +	drm_crtc_cleanup(crtc);
> +	WARN_ON(!kfifo_is_empty(&tilcdc_crtc->unref_fifo));
> +	kfifo_free(&tilcdc_crtc->unref_fifo);
> +	kfree(tilcdc_crtc);
> +}
> +
> +static int tilcdc_crtc_page_flip(struct drm_crtc *crtc,
> +		struct drm_framebuffer *fb,
> +		struct drm_pending_vblank_event *event)
> +{
> +	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> +	struct drm_device *dev = crtc->dev;
> +
> +	if (tilcdc_crtc->event) {
> +		dev_err(dev->dev, "already pending page flip!\n");
> +		return -EBUSY;
> +	}
> +
> +	crtc->fb = fb;
> +	tilcdc_crtc->event = event;
> +	update_scanout(crtc);
> +
> +	return 0;
> +}
> +
> +static void tilcdc_crtc_dpms(struct drm_crtc *crtc, int mode)
> +{
> +	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> +	struct drm_device *dev = crtc->dev;
> +	struct tilcdc_drm_private *priv = dev->dev_private;
> +
> +	/* we really only care about on or off: */
> +	if (mode != DRM_MODE_DPMS_ON)
> +		mode = DRM_MODE_DPMS_OFF;
> +
> +	if (tilcdc_crtc->dpms == mode)
> +		return;
> +
> +	tilcdc_crtc->dpms = mode;
> +
> +	pm_runtime_get_sync(dev->dev);
> +
> +	if (mode == DRM_MODE_DPMS_ON) {
> +		pm_runtime_forbid(dev->dev);
> +		start(crtc);
> +	} else {
> +		tilcdc_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(
> +					tilcdc_crtc->frame_done_wq,
> +					tilcdc_crtc->frame_done,
> +					msecs_to_jiffies(50));
> +			if (ret == 0)
> +				dev_err(dev->dev, "timeout waiting for framedone\n");
> +		}
> +		pm_runtime_allow(dev->dev);
> +	}
> +
> +	pm_runtime_put_sync(dev->dev);
> +}
> +
> +static bool tilcdc_crtc_mode_fixup(struct drm_crtc *crtc,
> +		const struct drm_display_mode *mode,
> +		struct drm_display_mode *adjusted_mode)
> +{
> +	return true;
> +}
> +
> +static void tilcdc_crtc_prepare(struct drm_crtc *crtc)
> +{
> +	tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
> +}
> +
> +static void tilcdc_crtc_commit(struct drm_crtc *crtc)
> +{
> +	tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
> +}
> +
> +static int tilcdc_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 tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> +	struct drm_device *dev = crtc->dev;
> +	struct tilcdc_drm_private *priv = dev->dev_private;
> +	const struct tilcdc_panel_info *info = tilcdc_crtc->info;
> +	uint32_t reg, hbp, hfp, hsw, vbp, vfp, vsw;
> +	int ret;
> +
> +	ret = tilcdc_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 = tilcdc_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);
> +	tilcdc_write(dev, LCDC_DMA_CTRL_REG, reg);
> +
> +	/* Configure the AC Bias Period and Number of Transitions per Interrupt: */
> +	reg = tilcdc_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);
> +	tilcdc_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;
> +
> +	DBG("%dx%d, hbp=%u, hfp=%u, hsw=%u, vbp=%u, vfp=%u, vsw=%u",
> +			mode->hdisplay, mode->vdisplay, hbp, hfp, hsw, vbp, vfp, vsw);
> +
> +	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;
> +	tilcdc_write(dev, LCDC_RASTER_TIMING_0_REG, reg);
> +
> +	reg = ((mode->vdisplay - 1) & 0x3ff) |
> +		((vbp & 0xff) << 24) |
> +		((vfp & 0xff) << 16) |
> +		((vsw & 0x3f) << 10);
> +	tilcdc_write(dev, LCDC_RASTER_TIMING_1_REG, reg);
> +
> +	/* Configure display type: */
> +	reg = tilcdc_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;
> +	tilcdc_write(dev, LCDC_RASTER_CTRL_REG, reg);
> +
> +	if (info->invert_pxl_clk)
> +		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK);
> +	else
> +		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK);
> +
> +	if (info->sync_ctrl)
> +		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL);
> +	else
> +		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL);
> +
> +	if (info->sync_edge)
> +		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE);
> +	else
> +		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE);
> +
> +	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
> +		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC);
> +	else
> +		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC);
> +
> +	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
> +		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC);
> +	else
> +		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC);
> +
> +	if (info->raster_order)
> +		tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER);
> +	else
> +		tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER);
> +
> +
> +	update_scanout(crtc);
> +	tilcdc_crtc_update_clk(crtc);
> +
> +	pm_runtime_put_sync(dev->dev);
> +
> +	return 0;
> +}
> +
> +static int tilcdc_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
> +		struct drm_framebuffer *old_fb)
> +{
> +	update_scanout(crtc);
> +	return 0;
> +}
> +
> +static void tilcdc_crtc_load_lut(struct drm_crtc *crtc)
> +{
> +}
> +
> +static const struct drm_crtc_funcs tilcdc_crtc_funcs = {
> +		.destroy        = tilcdc_crtc_destroy,
> +		.set_config     = drm_crtc_helper_set_config,
> +		.page_flip      = tilcdc_crtc_page_flip,
> +};
> +
> +static const struct drm_crtc_helper_funcs tilcdc_crtc_helper_funcs = {
> +		.dpms           = tilcdc_crtc_dpms,
> +		.mode_fixup     = tilcdc_crtc_mode_fixup,
> +		.prepare        = tilcdc_crtc_prepare,
> +		.commit         = tilcdc_crtc_commit,
> +		.mode_set       = tilcdc_crtc_mode_set,
> +		.mode_set_base  = tilcdc_crtc_mode_set_base,
> +		.load_lut       = tilcdc_crtc_load_lut,
> +};
> +
> +int tilcdc_crtc_max_width(struct drm_crtc *crtc)
> +{
> +	struct drm_device *dev = crtc->dev;
> +	struct tilcdc_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 tilcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode)
> +{
> +	struct tilcdc_drm_private *priv = crtc->dev->dev_private;
> +	unsigned int bandwidth;
> +
> +	if (mode->hdisplay > tilcdc_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;
> +
> +	/* filter out modes that would require too much memory bandwidth: */
> +	bandwidth = mode->hdisplay * mode->vdisplay * drm_mode_vrefresh(mode);
> +	if (bandwidth > priv->max_bandwidth)
> +		return MODE_BAD;
> +
> +	return MODE_OK;
> +}
> +
> +void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc,
> +		const struct tilcdc_panel_info *info)
> +{
> +	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> +	tilcdc_crtc->info = info;
> +}
> +
> +void tilcdc_crtc_update_clk(struct drm_crtc *crtc)
> +{
> +	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> +	struct drm_device *dev = crtc->dev;
> +	struct tilcdc_drm_private *priv = dev->dev_private;
> +	int dpms = tilcdc_crtc->dpms;
> +	unsigned int lcd_clk, div;
> +	int ret;
> +
> +	pm_runtime_get_sync(dev->dev);
> +
> +	if (dpms == DRM_MODE_DPMS_ON)
> +		tilcdc_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. */
> +	tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) |
> +			LCDC_RASTER_MODE);
> +
> +	if (priv->rev == 2)
> +		tilcdc_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)
> +		tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
> +
> +out:
> +	pm_runtime_put_sync(dev->dev);
> +}
> +
> +irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc)
> +{
> +	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> +	struct drm_device *dev = crtc->dev;
> +	struct tilcdc_drm_private *priv = dev->dev_private;
> +	uint32_t stat = tilcdc_read_irqstatus(dev);
> +
> +	if ((stat & LCDC_SYNC_LOST) && (stat & LCDC_FIFO_UNDERFLOW)) {
> +		stop(crtc);
> +		dev_err(dev->dev, "error: %08x\n", stat);
> +		tilcdc_clear_irqstatus(dev, stat);
> +		start(crtc);
> +	} else if (stat & LCDC_PL_LOAD_DONE) {
> +		tilcdc_clear_irqstatus(dev, stat);
> +	} else {
> +		struct drm_pending_vblank_event *event;
> +		unsigned long flags;
> +		uint32_t dirty = tilcdc_crtc->dirty & stat;
> +
> +		tilcdc_clear_irqstatus(dev, stat);
> +
> +		if (dirty & LCDC_END_OF_FRAME0)
> +			set_scanout(crtc, 0);
> +
> +		if (dirty & LCDC_END_OF_FRAME1)
> +			set_scanout(crtc, 1);
> +
> +		drm_handle_vblank(dev, 0);
> +
> +		spin_lock_irqsave(&dev->event_lock, flags);
> +		event = tilcdc_crtc->event;
> +		tilcdc_crtc->event = NULL;
> +		if (event)
> +			drm_send_vblank_event(dev, 0, event);
> +		spin_unlock_irqrestore(&dev->event_lock, flags);
> +
> +		if (dirty && !tilcdc_crtc->dirty)
> +			drm_vblank_put(dev, 0);
> +	}
> +
> +	if (priv->rev == 2) {
> +		if (stat & LCDC_FRAME_DONE) {
> +			tilcdc_crtc->frame_done = true;
> +			wake_up(&tilcdc_crtc->frame_done_wq);
> +		}
> +		tilcdc_write(dev, LCDC_END_OF_INT_IND_REG, 0);
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +void tilcdc_crtc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file)
> +{
> +	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_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 = tilcdc_crtc->event;
> +	if (event && event->base.file_priv == file) {
> +		tilcdc_crtc->event = NULL;
> +		event->base.destroy(&event->base);
> +		drm_vblank_put(dev, 0);
> +	}
> +	spin_unlock_irqrestore(&dev->event_lock, flags);
> +}

We need some common (helper function) solution for this kind of cleanup -
have the drivers have a copy&pasta version of it, the others miss it
completely. Maybe we need to keep them on a per-crtc list or something
like that ... Volunteered to look a bit into this?

[cut]

> +/*
> + * Power management:
> + */
> +
> +#if CONFIG_PM_SLEEP
> +static int tilcdc_pm_suspend(struct device *dev)
> +{
> +	struct drm_device *ddev = dev_get_drvdata(dev);
> +	struct tilcdc_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++] = tilcdc_read(ddev, registers[i].reg);
> +
> +	return 0;
> +}
> +
> +static int tilcdc_pm_resume(struct device *dev)
> +{
> +	struct drm_device *ddev = dev_get_drvdata(dev);
> +	struct tilcdc_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))
> +			tilcdc_write(ddev, registers[i].reg, priv->saved_register[n++]);
> +
> +	drm_kms_helper_poll_enable(ddev);
> +
> +	return 0;
> +}
> +#endif
> +
> +static const struct dev_pm_ops tilcdc_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(tilcdc_pm_suspend, tilcdc_pm_resume)
> +};

Mind the clueless but curious about platform pm stuff: Why are those
suspend/resume functions not associated with the platform device?

-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-22 23:41     ` Daniel Vetter
  0 siblings, 0 replies; 66+ messages in thread
From: Daniel Vetter @ 2013-01-22 23:41 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Jan 22, 2013 at 04:36:22PM -0600, 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.
> 
> v1: original
> v2: fix fb refcnting and few other cleanups
> v3: get +/- vsync/hsync from timings rather than panel-info, add
>     option DT max-bandwidth field so driver doesn't attempt to
>     pick a display mode with too high memory bandwidth, and other
>     small cleanups
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>

Ok, read through it, looks nice. No idea whether this should use the panel
stuff, but since no-one else does who cares. A few nits and questions
below. Was a nice reading to learn about kfifo and the runtime power
stuff.

Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>

> ---
>  drivers/gpu/drm/Kconfig                |   2 +
>  drivers/gpu/drm/Makefile               |   1 +
>  drivers/gpu/drm/tilcdc/Kconfig         |  10 +
>  drivers/gpu/drm/tilcdc/Makefile        |   8 +
>  drivers/gpu/drm/tilcdc/tilcdc_crtc.c   | 597 ++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_drv.c    | 605 +++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_drv.h    | 159 +++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_regs.h   | 154 +++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_tfp410.c | 423 +++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_tfp410.h |  26 ++
>  10 files changed, 1985 insertions(+)
>  create mode 100644 drivers/gpu/drm/tilcdc/Kconfig
>  create mode 100644 drivers/gpu/drm/tilcdc/Makefile
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_crtc.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.h
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_regs.h
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.h
> 

[cut]

> +struct tilcdc_crtc {
> +	struct drm_crtc base;
> +
> +	const struct tilcdc_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;
> +
> +	/* fb currently set to scanout 0/1: */
> +	struct drm_framebuffer *scanout[2];
> +
> +	/* for deferred fb unref's: */
> +	DECLARE_KFIFO_PTR(unref_fifo, struct drm_framebuffer *);
> +	struct work_struct work;
> +};
> +#define to_tilcdc_crtc(x) container_of(x, struct tilcdc_crtc, base)
> +
> +static void unref_worker(struct work_struct *work)
> +{
> +	struct tilcdc_crtc *tilcdc_crtc = container_of(work, struct tilcdc_crtc, work);
> +	struct drm_device *dev = tilcdc_crtc->base.dev;
> +	struct drm_framebuffer *fb;
> +
> +	mutex_lock(&dev->mode_config.mutex);
> +	while (kfifo_get(&tilcdc_crtc->unref_fifo, &fb))
> +		drm_framebuffer_unreference(fb);
> +	mutex_unlock(&dev->mode_config.mutex);

Hm, just learned about the kfifo api. It looks like the locking here still
works even with the new modeset locking, since kfifo explicitly allows
concurrent readers and writers without locking, as long as there's only on
of each kind. But maybe switch over to crtc->mutex to make things less
tricky.

Also, kfifo seems to have a new api which allows embedding of the kfifo
thing and needs to be used with kfifo_in/out.

[cut]

> +static void update_scanout(struct drm_crtc *crtc)
> +{
> +	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_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);
> +
> +	tilcdc_crtc->start = gem->paddr + fb->offsets[0] +
> +			(crtc->y * fb->pitches[0]) + (crtc->x * bpp/8);
> +
> +	tilcdc_crtc->end = tilcdc_crtc->start +
> +			(crtc->mode.vdisplay * fb->pitches[0]);
> +
> +	if (tilcdc_crtc->dpms == DRM_MODE_DPMS_ON) {
> +		/* already enabled, so just mark the frames that need
> +		 * updating and they will be updated on vblank:
> +		 */
> +		tilcdc_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, 0);
> +		set_scanout(crtc, 1);

At least on intel we disallow pageflips on disabled crtcs since
drm_vblank_get will fail. So dunno whether this is allowed on other
places, but in any case I don't see a drm_vblank_handle call, which means
we'll miss out on the pageflip completion event ...

> +	}
> +}
> +
> +static void start(struct drm_crtc *crtc)
> +{
> +	struct drm_device *dev = crtc->dev;
> +	struct tilcdc_drm_private *priv = dev->dev_private;
> +
> +	if (priv->rev == 2) {
> +		tilcdc_set(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET);
> +		msleep(1);
> +		tilcdc_clear(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET);
> +		msleep(1);
> +	}
> +
> +	tilcdc_set(dev, LCDC_DMA_CTRL_REG, LCDC_DUAL_FRAME_BUFFER_ENABLE);
> +	tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_PALETTE_LOAD_MODE(DATA_ONLY));
> +	tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
> +}
> +
> +static void stop(struct drm_crtc *crtc)
> +{
> +	struct drm_device *dev = crtc->dev;
> +
> +	tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
> +}
> +
> +static void tilcdc_crtc_destroy(struct drm_crtc *crtc)
> +{
> +	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> +
> +	WARN_ON(tilcdc_crtc->dpms == DRM_MODE_DPMS_ON);
> +
> +	drm_crtc_cleanup(crtc);
> +	WARN_ON(!kfifo_is_empty(&tilcdc_crtc->unref_fifo));
> +	kfifo_free(&tilcdc_crtc->unref_fifo);
> +	kfree(tilcdc_crtc);
> +}
> +
> +static int tilcdc_crtc_page_flip(struct drm_crtc *crtc,
> +		struct drm_framebuffer *fb,
> +		struct drm_pending_vblank_event *event)
> +{
> +	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> +	struct drm_device *dev = crtc->dev;
> +
> +	if (tilcdc_crtc->event) {
> +		dev_err(dev->dev, "already pending page flip!\n");
> +		return -EBUSY;
> +	}
> +
> +	crtc->fb = fb;
> +	tilcdc_crtc->event = event;
> +	update_scanout(crtc);
> +
> +	return 0;
> +}
> +
> +static void tilcdc_crtc_dpms(struct drm_crtc *crtc, int mode)
> +{
> +	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> +	struct drm_device *dev = crtc->dev;
> +	struct tilcdc_drm_private *priv = dev->dev_private;
> +
> +	/* we really only care about on or off: */
> +	if (mode != DRM_MODE_DPMS_ON)
> +		mode = DRM_MODE_DPMS_OFF;
> +
> +	if (tilcdc_crtc->dpms == mode)
> +		return;
> +
> +	tilcdc_crtc->dpms = mode;
> +
> +	pm_runtime_get_sync(dev->dev);
> +
> +	if (mode == DRM_MODE_DPMS_ON) {
> +		pm_runtime_forbid(dev->dev);
> +		start(crtc);
> +	} else {
> +		tilcdc_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(
> +					tilcdc_crtc->frame_done_wq,
> +					tilcdc_crtc->frame_done,
> +					msecs_to_jiffies(50));
> +			if (ret == 0)
> +				dev_err(dev->dev, "timeout waiting for framedone\n");
> +		}
> +		pm_runtime_allow(dev->dev);
> +	}
> +
> +	pm_runtime_put_sync(dev->dev);
> +}
> +
> +static bool tilcdc_crtc_mode_fixup(struct drm_crtc *crtc,
> +		const struct drm_display_mode *mode,
> +		struct drm_display_mode *adjusted_mode)
> +{
> +	return true;
> +}
> +
> +static void tilcdc_crtc_prepare(struct drm_crtc *crtc)
> +{
> +	tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
> +}
> +
> +static void tilcdc_crtc_commit(struct drm_crtc *crtc)
> +{
> +	tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
> +}
> +
> +static int tilcdc_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 tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> +	struct drm_device *dev = crtc->dev;
> +	struct tilcdc_drm_private *priv = dev->dev_private;
> +	const struct tilcdc_panel_info *info = tilcdc_crtc->info;
> +	uint32_t reg, hbp, hfp, hsw, vbp, vfp, vsw;
> +	int ret;
> +
> +	ret = tilcdc_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 = tilcdc_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);
> +	tilcdc_write(dev, LCDC_DMA_CTRL_REG, reg);
> +
> +	/* Configure the AC Bias Period and Number of Transitions per Interrupt: */
> +	reg = tilcdc_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);
> +	tilcdc_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;
> +
> +	DBG("%dx%d, hbp=%u, hfp=%u, hsw=%u, vbp=%u, vfp=%u, vsw=%u",
> +			mode->hdisplay, mode->vdisplay, hbp, hfp, hsw, vbp, vfp, vsw);
> +
> +	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;
> +	tilcdc_write(dev, LCDC_RASTER_TIMING_0_REG, reg);
> +
> +	reg = ((mode->vdisplay - 1) & 0x3ff) |
> +		((vbp & 0xff) << 24) |
> +		((vfp & 0xff) << 16) |
> +		((vsw & 0x3f) << 10);
> +	tilcdc_write(dev, LCDC_RASTER_TIMING_1_REG, reg);
> +
> +	/* Configure display type: */
> +	reg = tilcdc_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;
> +	tilcdc_write(dev, LCDC_RASTER_CTRL_REG, reg);
> +
> +	if (info->invert_pxl_clk)
> +		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK);
> +	else
> +		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK);
> +
> +	if (info->sync_ctrl)
> +		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL);
> +	else
> +		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL);
> +
> +	if (info->sync_edge)
> +		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE);
> +	else
> +		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE);
> +
> +	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
> +		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC);
> +	else
> +		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC);
> +
> +	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
> +		tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC);
> +	else
> +		tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC);
> +
> +	if (info->raster_order)
> +		tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER);
> +	else
> +		tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER);
> +
> +
> +	update_scanout(crtc);
> +	tilcdc_crtc_update_clk(crtc);
> +
> +	pm_runtime_put_sync(dev->dev);
> +
> +	return 0;
> +}
> +
> +static int tilcdc_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
> +		struct drm_framebuffer *old_fb)
> +{
> +	update_scanout(crtc);
> +	return 0;
> +}
> +
> +static void tilcdc_crtc_load_lut(struct drm_crtc *crtc)
> +{
> +}
> +
> +static const struct drm_crtc_funcs tilcdc_crtc_funcs = {
> +		.destroy        = tilcdc_crtc_destroy,
> +		.set_config     = drm_crtc_helper_set_config,
> +		.page_flip      = tilcdc_crtc_page_flip,
> +};
> +
> +static const struct drm_crtc_helper_funcs tilcdc_crtc_helper_funcs = {
> +		.dpms           = tilcdc_crtc_dpms,
> +		.mode_fixup     = tilcdc_crtc_mode_fixup,
> +		.prepare        = tilcdc_crtc_prepare,
> +		.commit         = tilcdc_crtc_commit,
> +		.mode_set       = tilcdc_crtc_mode_set,
> +		.mode_set_base  = tilcdc_crtc_mode_set_base,
> +		.load_lut       = tilcdc_crtc_load_lut,
> +};
> +
> +int tilcdc_crtc_max_width(struct drm_crtc *crtc)
> +{
> +	struct drm_device *dev = crtc->dev;
> +	struct tilcdc_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 tilcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode)
> +{
> +	struct tilcdc_drm_private *priv = crtc->dev->dev_private;
> +	unsigned int bandwidth;
> +
> +	if (mode->hdisplay > tilcdc_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;
> +
> +	/* filter out modes that would require too much memory bandwidth: */
> +	bandwidth = mode->hdisplay * mode->vdisplay * drm_mode_vrefresh(mode);
> +	if (bandwidth > priv->max_bandwidth)
> +		return MODE_BAD;
> +
> +	return MODE_OK;
> +}
> +
> +void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc,
> +		const struct tilcdc_panel_info *info)
> +{
> +	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> +	tilcdc_crtc->info = info;
> +}
> +
> +void tilcdc_crtc_update_clk(struct drm_crtc *crtc)
> +{
> +	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> +	struct drm_device *dev = crtc->dev;
> +	struct tilcdc_drm_private *priv = dev->dev_private;
> +	int dpms = tilcdc_crtc->dpms;
> +	unsigned int lcd_clk, div;
> +	int ret;
> +
> +	pm_runtime_get_sync(dev->dev);
> +
> +	if (dpms == DRM_MODE_DPMS_ON)
> +		tilcdc_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. */
> +	tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) |
> +			LCDC_RASTER_MODE);
> +
> +	if (priv->rev == 2)
> +		tilcdc_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)
> +		tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
> +
> +out:
> +	pm_runtime_put_sync(dev->dev);
> +}
> +
> +irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc)
> +{
> +	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
> +	struct drm_device *dev = crtc->dev;
> +	struct tilcdc_drm_private *priv = dev->dev_private;
> +	uint32_t stat = tilcdc_read_irqstatus(dev);
> +
> +	if ((stat & LCDC_SYNC_LOST) && (stat & LCDC_FIFO_UNDERFLOW)) {
> +		stop(crtc);
> +		dev_err(dev->dev, "error: %08x\n", stat);
> +		tilcdc_clear_irqstatus(dev, stat);
> +		start(crtc);
> +	} else if (stat & LCDC_PL_LOAD_DONE) {
> +		tilcdc_clear_irqstatus(dev, stat);
> +	} else {
> +		struct drm_pending_vblank_event *event;
> +		unsigned long flags;
> +		uint32_t dirty = tilcdc_crtc->dirty & stat;
> +
> +		tilcdc_clear_irqstatus(dev, stat);
> +
> +		if (dirty & LCDC_END_OF_FRAME0)
> +			set_scanout(crtc, 0);
> +
> +		if (dirty & LCDC_END_OF_FRAME1)
> +			set_scanout(crtc, 1);
> +
> +		drm_handle_vblank(dev, 0);
> +
> +		spin_lock_irqsave(&dev->event_lock, flags);
> +		event = tilcdc_crtc->event;
> +		tilcdc_crtc->event = NULL;
> +		if (event)
> +			drm_send_vblank_event(dev, 0, event);
> +		spin_unlock_irqrestore(&dev->event_lock, flags);
> +
> +		if (dirty && !tilcdc_crtc->dirty)
> +			drm_vblank_put(dev, 0);
> +	}
> +
> +	if (priv->rev == 2) {
> +		if (stat & LCDC_FRAME_DONE) {
> +			tilcdc_crtc->frame_done = true;
> +			wake_up(&tilcdc_crtc->frame_done_wq);
> +		}
> +		tilcdc_write(dev, LCDC_END_OF_INT_IND_REG, 0);
> +	}
> +
> +	return IRQ_HANDLED;
> +}
> +
> +void tilcdc_crtc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file)
> +{
> +	struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_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 = tilcdc_crtc->event;
> +	if (event && event->base.file_priv == file) {
> +		tilcdc_crtc->event = NULL;
> +		event->base.destroy(&event->base);
> +		drm_vblank_put(dev, 0);
> +	}
> +	spin_unlock_irqrestore(&dev->event_lock, flags);
> +}

We need some common (helper function) solution for this kind of cleanup -
have the drivers have a copy&pasta version of it, the others miss it
completely. Maybe we need to keep them on a per-crtc list or something
like that ... Volunteered to look a bit into this?

[cut]

> +/*
> + * Power management:
> + */
> +
> +#if CONFIG_PM_SLEEP
> +static int tilcdc_pm_suspend(struct device *dev)
> +{
> +	struct drm_device *ddev = dev_get_drvdata(dev);
> +	struct tilcdc_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++] = tilcdc_read(ddev, registers[i].reg);
> +
> +	return 0;
> +}
> +
> +static int tilcdc_pm_resume(struct device *dev)
> +{
> +	struct drm_device *ddev = dev_get_drvdata(dev);
> +	struct tilcdc_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))
> +			tilcdc_write(ddev, registers[i].reg, priv->saved_register[n++]);
> +
> +	drm_kms_helper_poll_enable(ddev);
> +
> +	return 0;
> +}
> +#endif
> +
> +static const struct dev_pm_ops tilcdc_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(tilcdc_pm_suspend, tilcdc_pm_resume)
> +};

Mind the clueless but curious about platform pm stuff: Why are those
suspend/resume functions not associated with the platform device?

-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
  2013-01-22 22:36   ` Rob Clark
@ 2013-01-23  7:43     ` Koen Kooi
  -1 siblings, 0 replies; 66+ messages in thread
From: Koen Kooi @ 2013-01-23  7:43 UTC (permalink / raw)
  To: Rob Clark; +Cc: dri-devel, patches, linux-omap, linux-arm-kernel


Op 22 jan. 2013, om 23:36 heeft Rob Clark <robdclark@gmail.com> het volgende geschreven:

> 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.
> 
> v1: original
> v2: fix fb refcnting and few other cleanups
> v3: get +/- vsync/hsync from timings rather than panel-info, add
>    option DT max-bandwidth field so driver doesn't attempt to
>    pick a display mode with too high memory bandwidth, and other
>    small cleanups
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>

Tested on a beaglebone and beaglebone-black:

Tested-by: Koen Kooi <koen@dominion.thruhere.net>

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-23  7:43     ` Koen Kooi
  0 siblings, 0 replies; 66+ messages in thread
From: Koen Kooi @ 2013-01-23  7:43 UTC (permalink / raw)
  To: linux-arm-kernel


Op 22 jan. 2013, om 23:36 heeft Rob Clark <robdclark@gmail.com> het volgende geschreven:

> 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.
> 
> v1: original
> v2: fix fb refcnting and few other cleanups
> v3: get +/- vsync/hsync from timings rather than panel-info, add
>    option DT max-bandwidth field so driver doesn't attempt to
>    pick a display mode with too high memory bandwidth, and other
>    small cleanups
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>

Tested on a beaglebone and beaglebone-black:

Tested-by: Koen Kooi <koen@dominion.thruhere.net>

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 2/4] drm/i2c: nxp-tda998x (v2)
  2013-01-22 22:36   ` Rob Clark
@ 2013-01-23  7:44     ` Koen Kooi
  -1 siblings, 0 replies; 66+ messages in thread
From: Koen Kooi @ 2013-01-23  7:44 UTC (permalink / raw)
  To: Rob Clark; +Cc: dri-devel, patches, linux-omap, linux-arm-kernel


Op 22 jan. 2013, om 23:36 heeft Rob Clark <robdclark@gmail.com> het volgende geschreven:

> Driver for the NXP TDA998X i2c hdmi encoder slave.
> 
> v1: original
> v2: fix npix/nline programming
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>

Tested on a  beaglebone-black:

Tested-by: Koen Kooi <koen@dominion.thruhere.net>


^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 2/4] drm/i2c: nxp-tda998x (v2)
@ 2013-01-23  7:44     ` Koen Kooi
  0 siblings, 0 replies; 66+ messages in thread
From: Koen Kooi @ 2013-01-23  7:44 UTC (permalink / raw)
  To: linux-arm-kernel


Op 22 jan. 2013, om 23:36 heeft Rob Clark <robdclark@gmail.com> het volgende geschreven:

> Driver for the NXP TDA998X i2c hdmi encoder slave.
> 
> v1: original
> v2: fix npix/nline programming
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>

Tested on a  beaglebone-black:

Tested-by: Koen Kooi <koen@dominion.thruhere.net>

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 0/4] TI LCDC DRM driver
  2013-01-22 22:36 ` Rob Clark
@ 2013-01-23  7:48   ` Sascha Hauer
  -1 siblings, 0 replies; 66+ messages in thread
From: Sascha Hauer @ 2013-01-23  7:48 UTC (permalink / raw)
  To: Rob Clark; +Cc: dri-devel, linux-omap, linux-arm-kernel, patches

Hi Rob,

On Tue, Jan 22, 2013 at 04:36:21PM -0600, Rob Clark wrote:
> 
>  drivers/gpu/drm/Kconfig                |   2 +
>  drivers/gpu/drm/Makefile               |   1 +
>  drivers/gpu/drm/i2c/Makefile           |   3 +
>  drivers/gpu/drm/i2c/tda998x_drv.c      | 908 +++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/Kconfig         |  25 +
>  drivers/gpu/drm/tilcdc/Makefile        |  10 +
>  drivers/gpu/drm/tilcdc/tilcdc_crtc.c   | 597 ++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_drv.c    | 611 ++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_drv.h    | 159 ++++++
>  drivers/gpu/drm/tilcdc/tilcdc_panel.c  | 443 ++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_panel.h  |  26 +
>  drivers/gpu/drm/tilcdc/tilcdc_regs.h   | 154 ++++++
>  drivers/gpu/drm/tilcdc/tilcdc_slave.c  | 380 ++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_slave.h  |  26 +
>  drivers/gpu/drm/tilcdc/tilcdc_tfp410.c | 423 +++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_tfp410.h |  26 +
>  16 files changed, 3794 insertions(+)
>  create mode 100644 drivers/gpu/drm/i2c/tda998x_drv.c
>  create mode 100644 drivers/gpu/drm/tilcdc/Kconfig
>  create mode 100644 drivers/gpu/drm/tilcdc/Makefile
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_crtc.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.h
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_panel.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_panel.h
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_regs.h
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.h
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.h

I'm missing Documentation/devicetree/bindings/drm/tilcdc/ in the
diffstat. This is required for adding devicetree bindings.

Sascha

-- 
Pengutronix e.K.                           |                             |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 0/4] TI LCDC DRM driver
@ 2013-01-23  7:48   ` Sascha Hauer
  0 siblings, 0 replies; 66+ messages in thread
From: Sascha Hauer @ 2013-01-23  7:48 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Rob,

On Tue, Jan 22, 2013 at 04:36:21PM -0600, Rob Clark wrote:
> 
>  drivers/gpu/drm/Kconfig                |   2 +
>  drivers/gpu/drm/Makefile               |   1 +
>  drivers/gpu/drm/i2c/Makefile           |   3 +
>  drivers/gpu/drm/i2c/tda998x_drv.c      | 908 +++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/Kconfig         |  25 +
>  drivers/gpu/drm/tilcdc/Makefile        |  10 +
>  drivers/gpu/drm/tilcdc/tilcdc_crtc.c   | 597 ++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_drv.c    | 611 ++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_drv.h    | 159 ++++++
>  drivers/gpu/drm/tilcdc/tilcdc_panel.c  | 443 ++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_panel.h  |  26 +
>  drivers/gpu/drm/tilcdc/tilcdc_regs.h   | 154 ++++++
>  drivers/gpu/drm/tilcdc/tilcdc_slave.c  | 380 ++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_slave.h  |  26 +
>  drivers/gpu/drm/tilcdc/tilcdc_tfp410.c | 423 +++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_tfp410.h |  26 +
>  16 files changed, 3794 insertions(+)
>  create mode 100644 drivers/gpu/drm/i2c/tda998x_drv.c
>  create mode 100644 drivers/gpu/drm/tilcdc/Kconfig
>  create mode 100644 drivers/gpu/drm/tilcdc/Makefile
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_crtc.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.h
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_panel.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_panel.h
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_regs.h
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.h
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.h

I'm missing Documentation/devicetree/bindings/drm/tilcdc/ in the
diffstat. This is required for adding devicetree bindings.

Sascha

-- 
Pengutronix e.K.                           |                             |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
  2013-01-22 22:36   ` Rob Clark
@ 2013-01-23  9:42     ` Jean-Francois Moine
  -1 siblings, 0 replies; 66+ messages in thread
From: Jean-Francois Moine @ 2013-01-23  9:42 UTC (permalink / raw)
  To: Rob Clark; +Cc: dri-devel, linux-omap, linux-arm-kernel, patches

Hi Rob,

As I wanted to re-use your nxp-tda998x driver for the Marvell Dove SoC,
I had a look at your IT LCD driver. Comments below.

On Tue, 22 Jan 2013 16:36:22 -0600
Rob Clark <robdclark@gmail.com> 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.
> 
> v1: original
> v2: fix fb refcnting and few other cleanups
> v3: get +/- vsync/hsync from timings rather than panel-info, add
>     option DT max-bandwidth field so driver doesn't attempt to
>     pick a display mode with too high memory bandwidth, and other
>     small cleanups
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>
> ---
>  drivers/gpu/drm/Kconfig                |   2 +
>  drivers/gpu/drm/Makefile               |   1 +
>  drivers/gpu/drm/tilcdc/Kconfig         |  10 +
>  drivers/gpu/drm/tilcdc/Makefile        |   8 +
>  drivers/gpu/drm/tilcdc/tilcdc_crtc.c   | 597 ++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_drv.c    | 605 +++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_drv.h    | 159 +++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_regs.h   | 154 +++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_tfp410.c | 423 +++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_tfp410.h |  26 ++
>  10 files changed, 1985 insertions(+)
>  create mode 100644 drivers/gpu/drm/tilcdc/Kconfig
>  create mode 100644 drivers/gpu/drm/tilcdc/Makefile
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_crtc.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.h
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_regs.h
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.h
	[snip]
> diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig
> new file mode 100644
> index 0000000..ee9b592
> --- /dev/null
> +++ b/drivers/gpu/drm/tilcdc/Kconfig
> @@ -0,0 +1,10 @@
> +config DRM_TILCDC
> +	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/tilcdc/Makefile b/drivers/gpu/drm/tilcdc/Makefile
> new file mode 100644
> index 0000000..1359cc2
> --- /dev/null
> +++ b/drivers/gpu/drm/tilcdc/Makefile
> @@ -0,0 +1,8 @@
> +ccflags-y := -Iinclude/drm -Werror
> +
> +tilcdc-y := \
> +	tilcdc_crtc.o \
> +	tilcdc_tfp410.o \
> +	tilcdc_drv.o

As I understand, this means that the 3 objects will go into the
resident kernel.

I though that the device tree was created for Linux distributors who
might generate generic ARM kernels, the choice of the drivers being
done according the local device tree.

From this point of vue, it would be nicer to have 2 separate modules:
tilcdc and tfp410, tilcdc_crtc being included in one of these ones
(I did not look deep enough at the code to know which).

	[snip]
> diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
> new file mode 100644
> index 0000000..cf1fddc
> --- /dev/null
> +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
> @@ -0,0 +1,605 @@
	[snip]
> +static struct drm_driver tilcdc_driver = {
> +	.driver_features    = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET,
> +	.load               = tilcdc_load,
> +	.unload             = tilcdc_unload,
> +	.preclose           = tilcdc_preclose,
> +	.lastclose          = tilcdc_lastclose,
> +	.irq_handler        = tilcdc_irq,
> +	.irq_preinstall     = tilcdc_irq_preinstall,
> +	.irq_postinstall    = tilcdc_irq_postinstall,
> +	.irq_uninstall      = tilcdc_irq_uninstall,
> +	.get_vblank_counter = drm_vblank_count,
> +	.enable_vblank      = tilcdc_enable_vblank,
> +	.disable_vblank     = tilcdc_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       = tilcdc_debugfs_init,
> +	.debugfs_cleanup    = tilcdc_debugfs_cleanup,
> +#endif
> +	.fops               = &fops,
> +	.name               = "tilcdc",
> +	.desc               = "TI LCD Controller DRM",
> +	.date               = "20121205",
> +	.major              = 1,
> +	.minor              = 0,
> +};
> +
> +/*
> + * Power management:
> + */
> +
> +#if CONFIG_PM_SLEEP

Should be:

#ifdef CONFIG_PM_SLEEP

	[snip]
> +static struct of_device_id tilcdc_of_match[] = {
> +		{ .compatible = "ti,am33xx-tilcdc", },
> +		{ },
> +};
> +MODULE_DEVICE_TABLE(of, tilcdc_of_match);
> +
> +static struct platform_driver tilcdc_platform_driver = {
> +	.probe      = tilcdc_pdev_probe,
> +	.remove     = tilcdc_pdev_remove,
> +	.driver     = {
> +		.owner  = THIS_MODULE,
> +		.name   = "tilcdc",
> +		.pm     = &tilcdc_pm_ops,
> +		.of_match_table = tilcdc_of_match,
> +	},
> +};
> +
> +static int __init tilcdc_drm_init(void)
> +{
> +	DBG("init");
> +	tilcdc_tfp410_init();

This function may be called twice if "tilcdc,tfp410" is in the DT.

> +	return platform_driver_register(&tilcdc_platform_driver);
> +}
> +
> +static void __exit tilcdc_drm_fini(void)
> +{
> +	DBG("fini");
> +	tilcdc_tfp410_fini();
> +	platform_driver_unregister(&tilcdc_platform_driver);
> +}
> +
> +module_init(tilcdc_drm_init);
> +module_exit(tilcdc_drm_fini);
> +
> +MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
> +MODULE_DESCRIPTION("TI LCD Controller DRM Driver");
> +MODULE_LICENSE("GPL");


-- 
Ken ar c'hentañ	|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/
--
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

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-23  9:42     ` Jean-Francois Moine
  0 siblings, 0 replies; 66+ messages in thread
From: Jean-Francois Moine @ 2013-01-23  9:42 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Rob,

As I wanted to re-use your nxp-tda998x driver for the Marvell Dove SoC,
I had a look at your IT LCD driver. Comments below.

On Tue, 22 Jan 2013 16:36:22 -0600
Rob Clark <robdclark@gmail.com> 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.
> 
> v1: original
> v2: fix fb refcnting and few other cleanups
> v3: get +/- vsync/hsync from timings rather than panel-info, add
>     option DT max-bandwidth field so driver doesn't attempt to
>     pick a display mode with too high memory bandwidth, and other
>     small cleanups
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>
> ---
>  drivers/gpu/drm/Kconfig                |   2 +
>  drivers/gpu/drm/Makefile               |   1 +
>  drivers/gpu/drm/tilcdc/Kconfig         |  10 +
>  drivers/gpu/drm/tilcdc/Makefile        |   8 +
>  drivers/gpu/drm/tilcdc/tilcdc_crtc.c   | 597 ++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_drv.c    | 605 +++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_drv.h    | 159 +++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_regs.h   | 154 +++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_tfp410.c | 423 +++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_tfp410.h |  26 ++
>  10 files changed, 1985 insertions(+)
>  create mode 100644 drivers/gpu/drm/tilcdc/Kconfig
>  create mode 100644 drivers/gpu/drm/tilcdc/Makefile
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_crtc.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.h
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_regs.h
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.h
	[snip]
> diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig
> new file mode 100644
> index 0000000..ee9b592
> --- /dev/null
> +++ b/drivers/gpu/drm/tilcdc/Kconfig
> @@ -0,0 +1,10 @@
> +config DRM_TILCDC
> +	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/tilcdc/Makefile b/drivers/gpu/drm/tilcdc/Makefile
> new file mode 100644
> index 0000000..1359cc2
> --- /dev/null
> +++ b/drivers/gpu/drm/tilcdc/Makefile
> @@ -0,0 +1,8 @@
> +ccflags-y := -Iinclude/drm -Werror
> +
> +tilcdc-y := \
> +	tilcdc_crtc.o \
> +	tilcdc_tfp410.o \
> +	tilcdc_drv.o

As I understand, this means that the 3 objects will go into the
resident kernel.

I though that the device tree was created for Linux distributors who
might generate generic ARM kernels, the choice of the drivers being
done according the local device tree.

>From this point of vue, it would be nicer to have 2 separate modules:
tilcdc and tfp410, tilcdc_crtc being included in one of these ones
(I did not look deep enough at the code to know which).

	[snip]
> diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
> new file mode 100644
> index 0000000..cf1fddc
> --- /dev/null
> +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
> @@ -0,0 +1,605 @@
	[snip]
> +static struct drm_driver tilcdc_driver = {
> +	.driver_features    = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET,
> +	.load               = tilcdc_load,
> +	.unload             = tilcdc_unload,
> +	.preclose           = tilcdc_preclose,
> +	.lastclose          = tilcdc_lastclose,
> +	.irq_handler        = tilcdc_irq,
> +	.irq_preinstall     = tilcdc_irq_preinstall,
> +	.irq_postinstall    = tilcdc_irq_postinstall,
> +	.irq_uninstall      = tilcdc_irq_uninstall,
> +	.get_vblank_counter = drm_vblank_count,
> +	.enable_vblank      = tilcdc_enable_vblank,
> +	.disable_vblank     = tilcdc_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       = tilcdc_debugfs_init,
> +	.debugfs_cleanup    = tilcdc_debugfs_cleanup,
> +#endif
> +	.fops               = &fops,
> +	.name               = "tilcdc",
> +	.desc               = "TI LCD Controller DRM",
> +	.date               = "20121205",
> +	.major              = 1,
> +	.minor              = 0,
> +};
> +
> +/*
> + * Power management:
> + */
> +
> +#if CONFIG_PM_SLEEP

Should be:

#ifdef CONFIG_PM_SLEEP

	[snip]
> +static struct of_device_id tilcdc_of_match[] = {
> +		{ .compatible = "ti,am33xx-tilcdc", },
> +		{ },
> +};
> +MODULE_DEVICE_TABLE(of, tilcdc_of_match);
> +
> +static struct platform_driver tilcdc_platform_driver = {
> +	.probe      = tilcdc_pdev_probe,
> +	.remove     = tilcdc_pdev_remove,
> +	.driver     = {
> +		.owner  = THIS_MODULE,
> +		.name   = "tilcdc",
> +		.pm     = &tilcdc_pm_ops,
> +		.of_match_table = tilcdc_of_match,
> +	},
> +};
> +
> +static int __init tilcdc_drm_init(void)
> +{
> +	DBG("init");
> +	tilcdc_tfp410_init();

This function may be called twice if "tilcdc,tfp410" is in the DT.

> +	return platform_driver_register(&tilcdc_platform_driver);
> +}
> +
> +static void __exit tilcdc_drm_fini(void)
> +{
> +	DBG("fini");
> +	tilcdc_tfp410_fini();
> +	platform_driver_unregister(&tilcdc_platform_driver);
> +}
> +
> +module_init(tilcdc_drm_init);
> +module_exit(tilcdc_drm_fini);
> +
> +MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
> +MODULE_DESCRIPTION("TI LCD Controller DRM Driver");
> +MODULE_LICENSE("GPL");


-- 
Ken ar c'henta?	|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 2/4] drm/i2c: nxp-tda998x (v2)
  2013-01-22 22:36   ` Rob Clark
@ 2013-01-23  9:42     ` Jean-Francois Moine
  -1 siblings, 0 replies; 66+ messages in thread
From: Jean-Francois Moine @ 2013-01-23  9:42 UTC (permalink / raw)
  To: Rob Clark; +Cc: dri-devel, linux-omap, linux-arm-kernel, patches

On Tue, 22 Jan 2013 16:36:23 -0600
Rob Clark <robdclark@gmail.com> wrote:

> Driver for the NXP TDA998X i2c hdmi encoder slave.
> 
> v1: original
> v2: fix npix/nline programming
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>
> ---
>  drivers/gpu/drm/i2c/Makefile      |   3 +
>  drivers/gpu/drm/i2c/tda998x_drv.c | 908 ++++++++++++++++++++++++++++++++++++++
>  2 files changed, 911 insertions(+)
>  create mode 100644 drivers/gpu/drm/i2c/tda998x_drv.c
	[snip]
> diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
> new file mode 100644
> index 0000000..02054e8
> --- /dev/null
> +++ b/drivers/gpu/drm/i2c/tda998x_drv.c
> @@ -0,0 +1,908 @@
	[snip]
> +static void __exit
> +tda998x_exit(void)
> +{
> +	DBG("");
> +	drm_i2c_encoder_unregister(&tda998x_driver);
> +}
> +
> +MODULE_DESCRIPTION("NXP Semiconductors TDA998X TMDS transmitter driver");
> +
> +MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
> +MODULE_DESCRIPTION("NXP Semiconductors TDA998X HDMI Encoder");
> +MODULE_LICENSE("GPL");
> +
> +module_init(tda998x_init);
> +module_exit(tda998x_exit);

There are 2 MODULE_DESCRIPTION().

-- 
Ken ar c'hentañ	|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/
--
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

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 2/4] drm/i2c: nxp-tda998x (v2)
@ 2013-01-23  9:42     ` Jean-Francois Moine
  0 siblings, 0 replies; 66+ messages in thread
From: Jean-Francois Moine @ 2013-01-23  9:42 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, 22 Jan 2013 16:36:23 -0600
Rob Clark <robdclark@gmail.com> wrote:

> Driver for the NXP TDA998X i2c hdmi encoder slave.
> 
> v1: original
> v2: fix npix/nline programming
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>
> ---
>  drivers/gpu/drm/i2c/Makefile      |   3 +
>  drivers/gpu/drm/i2c/tda998x_drv.c | 908 ++++++++++++++++++++++++++++++++++++++
>  2 files changed, 911 insertions(+)
>  create mode 100644 drivers/gpu/drm/i2c/tda998x_drv.c
	[snip]
> diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
> new file mode 100644
> index 0000000..02054e8
> --- /dev/null
> +++ b/drivers/gpu/drm/i2c/tda998x_drv.c
> @@ -0,0 +1,908 @@
	[snip]
> +static void __exit
> +tda998x_exit(void)
> +{
> +	DBG("");
> +	drm_i2c_encoder_unregister(&tda998x_driver);
> +}
> +
> +MODULE_DESCRIPTION("NXP Semiconductors TDA998X TMDS transmitter driver");
> +
> +MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
> +MODULE_DESCRIPTION("NXP Semiconductors TDA998X HDMI Encoder");
> +MODULE_LICENSE("GPL");
> +
> +module_init(tda998x_init);
> +module_exit(tda998x_exit);

There are 2 MODULE_DESCRIPTION().

-- 
Ken ar c'henta?	|	      ** Breizh ha Linux atav! **
Jef		|		http://moinejf.free.fr/

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
  2013-01-23  9:42     ` Jean-Francois Moine
@ 2013-01-23 13:24       ` Rob Clark
  -1 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-23 13:24 UTC (permalink / raw)
  To: Jean-Francois Moine; +Cc: dri-devel, linux-omap, linux-arm-kernel, patches

On Wed, Jan 23, 2013 at 3:42 AM, Jean-Francois Moine <moinejf@free.fr> wrote:
> Hi Rob,
>
> As I wanted to re-use your nxp-tda998x driver for the Marvell Dove SoC,
> I had a look at your IT LCD driver. Comments below.

Just fyi, you can re-use the nxp-tda998x part independently of tilcdc
(just in case that wasn't clear).

It may be that we need to add some configuration info struct, if for
example there are some differences in video signal mux on the dove
board, but that shouldn't be a big deal.  I figure we can see what is
needed as others start to use it and add as needed.

> On Tue, 22 Jan 2013 16:36:22 -0600
> Rob Clark <robdclark@gmail.com> 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.
>>
>> v1: original
>> v2: fix fb refcnting and few other cleanups
>> v3: get +/- vsync/hsync from timings rather than panel-info, add
>>     option DT max-bandwidth field so driver doesn't attempt to
>>     pick a display mode with too high memory bandwidth, and other
>>     small cleanups
>>
>> Signed-off-by: Rob Clark <robdclark@gmail.com>
>> ---
>>  drivers/gpu/drm/Kconfig                |   2 +
>>  drivers/gpu/drm/Makefile               |   1 +
>>  drivers/gpu/drm/tilcdc/Kconfig         |  10 +
>>  drivers/gpu/drm/tilcdc/Makefile        |   8 +
>>  drivers/gpu/drm/tilcdc/tilcdc_crtc.c   | 597 ++++++++++++++++++++++++++++++++
>>  drivers/gpu/drm/tilcdc/tilcdc_drv.c    | 605 +++++++++++++++++++++++++++++++++
>>  drivers/gpu/drm/tilcdc/tilcdc_drv.h    | 159 +++++++++
>>  drivers/gpu/drm/tilcdc/tilcdc_regs.h   | 154 +++++++++
>>  drivers/gpu/drm/tilcdc/tilcdc_tfp410.c | 423 +++++++++++++++++++++++
>>  drivers/gpu/drm/tilcdc/tilcdc_tfp410.h |  26 ++
>>  10 files changed, 1985 insertions(+)
>>  create mode 100644 drivers/gpu/drm/tilcdc/Kconfig
>>  create mode 100644 drivers/gpu/drm/tilcdc/Makefile
>>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_crtc.c
>>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.c
>>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.h
>>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_regs.h
>>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
>>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.h
>         [snip]
>> diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig
>> new file mode 100644
>> index 0000000..ee9b592
>> --- /dev/null
>> +++ b/drivers/gpu/drm/tilcdc/Kconfig
>> @@ -0,0 +1,10 @@
>> +config DRM_TILCDC
>> +     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/tilcdc/Makefile b/drivers/gpu/drm/tilcdc/Makefile
>> new file mode 100644
>> index 0000000..1359cc2
>> --- /dev/null
>> +++ b/drivers/gpu/drm/tilcdc/Makefile
>> @@ -0,0 +1,8 @@
>> +ccflags-y := -Iinclude/drm -Werror
>> +
>> +tilcdc-y := \
>> +     tilcdc_crtc.o \
>> +     tilcdc_tfp410.o \
>> +     tilcdc_drv.o
>
> As I understand, this means that the 3 objects will go into the
> resident kernel.
>
> I though that the device tree was created for Linux distributors who
> might generate generic ARM kernels, the choice of the drivers being
> done according the local device tree.
>
> From this point of vue, it would be nicer to have 2 separate modules:
> tilcdc and tfp410, tilcdc_crtc being included in one of these ones
> (I did not look deep enough at the code to know which).

drv and crtc are the "core" driver... arguably the tfp410 part could
be optional.  I'd prefer *not* as a separate module, as this implies
some independence of module lifetime, which is not true.  I generally
prefer to start simple and add complexity later if someone really
thinks they need it, which is why I avoided adding a bunch of kconfig
options to start with.

>         [snip]
>> diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
>> new file mode 100644
>> index 0000000..cf1fddc
>> --- /dev/null
>> +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
>> @@ -0,0 +1,605 @@
>         [snip]
>> +static struct drm_driver tilcdc_driver = {
>> +     .driver_features    = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET,
>> +     .load               = tilcdc_load,
>> +     .unload             = tilcdc_unload,
>> +     .preclose           = tilcdc_preclose,
>> +     .lastclose          = tilcdc_lastclose,
>> +     .irq_handler        = tilcdc_irq,
>> +     .irq_preinstall     = tilcdc_irq_preinstall,
>> +     .irq_postinstall    = tilcdc_irq_postinstall,
>> +     .irq_uninstall      = tilcdc_irq_uninstall,
>> +     .get_vblank_counter = drm_vblank_count,
>> +     .enable_vblank      = tilcdc_enable_vblank,
>> +     .disable_vblank     = tilcdc_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       = tilcdc_debugfs_init,
>> +     .debugfs_cleanup    = tilcdc_debugfs_cleanup,
>> +#endif
>> +     .fops               = &fops,
>> +     .name               = "tilcdc",
>> +     .desc               = "TI LCD Controller DRM",
>> +     .date               = "20121205",
>> +     .major              = 1,
>> +     .minor              = 0,
>> +};
>> +
>> +/*
>> + * Power management:
>> + */
>> +
>> +#if CONFIG_PM_SLEEP
>
> Should be:
>
> #ifdef CONFIG_PM_SLEEP
>
>         [snip]
>> +static struct of_device_id tilcdc_of_match[] = {
>> +             { .compatible = "ti,am33xx-tilcdc", },
>> +             { },
>> +};
>> +MODULE_DEVICE_TABLE(of, tilcdc_of_match);
>> +
>> +static struct platform_driver tilcdc_platform_driver = {
>> +     .probe      = tilcdc_pdev_probe,
>> +     .remove     = tilcdc_pdev_remove,
>> +     .driver     = {
>> +             .owner  = THIS_MODULE,
>> +             .name   = "tilcdc",
>> +             .pm     = &tilcdc_pm_ops,
>> +             .of_match_table = tilcdc_of_match,
>> +     },
>> +};
>> +
>> +static int __init tilcdc_drm_init(void)
>> +{
>> +     DBG("init");
>> +     tilcdc_tfp410_init();
>
> This function may be called twice if "tilcdc,tfp410" is in the DT.

hmm, I don't think it was but I'll have to double check that.  At
least if it was it wasn't seeming to cause anything bad, because I did
have "tilcdc,tfp410" in DT..

BR,
-R

>> +     return platform_driver_register(&tilcdc_platform_driver);
>> +}
>> +
>> +static void __exit tilcdc_drm_fini(void)
>> +{
>> +     DBG("fini");
>> +     tilcdc_tfp410_fini();
>> +     platform_driver_unregister(&tilcdc_platform_driver);
>> +}
>> +
>> +module_init(tilcdc_drm_init);
>> +module_exit(tilcdc_drm_fini);
>> +
>> +MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
>> +MODULE_DESCRIPTION("TI LCD Controller DRM Driver");
>> +MODULE_LICENSE("GPL");
>
>
> --
> Ken ar c'hentañ |             ** Breizh ha Linux atav! **
> Jef             |               http://moinejf.free.fr/
--
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

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-23 13:24       ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-23 13:24 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Jan 23, 2013 at 3:42 AM, Jean-Francois Moine <moinejf@free.fr> wrote:
> Hi Rob,
>
> As I wanted to re-use your nxp-tda998x driver for the Marvell Dove SoC,
> I had a look at your IT LCD driver. Comments below.

Just fyi, you can re-use the nxp-tda998x part independently of tilcdc
(just in case that wasn't clear).

It may be that we need to add some configuration info struct, if for
example there are some differences in video signal mux on the dove
board, but that shouldn't be a big deal.  I figure we can see what is
needed as others start to use it and add as needed.

> On Tue, 22 Jan 2013 16:36:22 -0600
> Rob Clark <robdclark@gmail.com> 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.
>>
>> v1: original
>> v2: fix fb refcnting and few other cleanups
>> v3: get +/- vsync/hsync from timings rather than panel-info, add
>>     option DT max-bandwidth field so driver doesn't attempt to
>>     pick a display mode with too high memory bandwidth, and other
>>     small cleanups
>>
>> Signed-off-by: Rob Clark <robdclark@gmail.com>
>> ---
>>  drivers/gpu/drm/Kconfig                |   2 +
>>  drivers/gpu/drm/Makefile               |   1 +
>>  drivers/gpu/drm/tilcdc/Kconfig         |  10 +
>>  drivers/gpu/drm/tilcdc/Makefile        |   8 +
>>  drivers/gpu/drm/tilcdc/tilcdc_crtc.c   | 597 ++++++++++++++++++++++++++++++++
>>  drivers/gpu/drm/tilcdc/tilcdc_drv.c    | 605 +++++++++++++++++++++++++++++++++
>>  drivers/gpu/drm/tilcdc/tilcdc_drv.h    | 159 +++++++++
>>  drivers/gpu/drm/tilcdc/tilcdc_regs.h   | 154 +++++++++
>>  drivers/gpu/drm/tilcdc/tilcdc_tfp410.c | 423 +++++++++++++++++++++++
>>  drivers/gpu/drm/tilcdc/tilcdc_tfp410.h |  26 ++
>>  10 files changed, 1985 insertions(+)
>>  create mode 100644 drivers/gpu/drm/tilcdc/Kconfig
>>  create mode 100644 drivers/gpu/drm/tilcdc/Makefile
>>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_crtc.c
>>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.c
>>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_drv.h
>>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_regs.h
>>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
>>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_tfp410.h
>         [snip]
>> diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig
>> new file mode 100644
>> index 0000000..ee9b592
>> --- /dev/null
>> +++ b/drivers/gpu/drm/tilcdc/Kconfig
>> @@ -0,0 +1,10 @@
>> +config DRM_TILCDC
>> +     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/tilcdc/Makefile b/drivers/gpu/drm/tilcdc/Makefile
>> new file mode 100644
>> index 0000000..1359cc2
>> --- /dev/null
>> +++ b/drivers/gpu/drm/tilcdc/Makefile
>> @@ -0,0 +1,8 @@
>> +ccflags-y := -Iinclude/drm -Werror
>> +
>> +tilcdc-y := \
>> +     tilcdc_crtc.o \
>> +     tilcdc_tfp410.o \
>> +     tilcdc_drv.o
>
> As I understand, this means that the 3 objects will go into the
> resident kernel.
>
> I though that the device tree was created for Linux distributors who
> might generate generic ARM kernels, the choice of the drivers being
> done according the local device tree.
>
> From this point of vue, it would be nicer to have 2 separate modules:
> tilcdc and tfp410, tilcdc_crtc being included in one of these ones
> (I did not look deep enough at the code to know which).

drv and crtc are the "core" driver... arguably the tfp410 part could
be optional.  I'd prefer *not* as a separate module, as this implies
some independence of module lifetime, which is not true.  I generally
prefer to start simple and add complexity later if someone really
thinks they need it, which is why I avoided adding a bunch of kconfig
options to start with.

>         [snip]
>> diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
>> new file mode 100644
>> index 0000000..cf1fddc
>> --- /dev/null
>> +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
>> @@ -0,0 +1,605 @@
>         [snip]
>> +static struct drm_driver tilcdc_driver = {
>> +     .driver_features    = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET,
>> +     .load               = tilcdc_load,
>> +     .unload             = tilcdc_unload,
>> +     .preclose           = tilcdc_preclose,
>> +     .lastclose          = tilcdc_lastclose,
>> +     .irq_handler        = tilcdc_irq,
>> +     .irq_preinstall     = tilcdc_irq_preinstall,
>> +     .irq_postinstall    = tilcdc_irq_postinstall,
>> +     .irq_uninstall      = tilcdc_irq_uninstall,
>> +     .get_vblank_counter = drm_vblank_count,
>> +     .enable_vblank      = tilcdc_enable_vblank,
>> +     .disable_vblank     = tilcdc_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       = tilcdc_debugfs_init,
>> +     .debugfs_cleanup    = tilcdc_debugfs_cleanup,
>> +#endif
>> +     .fops               = &fops,
>> +     .name               = "tilcdc",
>> +     .desc               = "TI LCD Controller DRM",
>> +     .date               = "20121205",
>> +     .major              = 1,
>> +     .minor              = 0,
>> +};
>> +
>> +/*
>> + * Power management:
>> + */
>> +
>> +#if CONFIG_PM_SLEEP
>
> Should be:
>
> #ifdef CONFIG_PM_SLEEP
>
>         [snip]
>> +static struct of_device_id tilcdc_of_match[] = {
>> +             { .compatible = "ti,am33xx-tilcdc", },
>> +             { },
>> +};
>> +MODULE_DEVICE_TABLE(of, tilcdc_of_match);
>> +
>> +static struct platform_driver tilcdc_platform_driver = {
>> +     .probe      = tilcdc_pdev_probe,
>> +     .remove     = tilcdc_pdev_remove,
>> +     .driver     = {
>> +             .owner  = THIS_MODULE,
>> +             .name   = "tilcdc",
>> +             .pm     = &tilcdc_pm_ops,
>> +             .of_match_table = tilcdc_of_match,
>> +     },
>> +};
>> +
>> +static int __init tilcdc_drm_init(void)
>> +{
>> +     DBG("init");
>> +     tilcdc_tfp410_init();
>
> This function may be called twice if "tilcdc,tfp410" is in the DT.

hmm, I don't think it was but I'll have to double check that.  At
least if it was it wasn't seeming to cause anything bad, because I did
have "tilcdc,tfp410" in DT..

BR,
-R

>> +     return platform_driver_register(&tilcdc_platform_driver);
>> +}
>> +
>> +static void __exit tilcdc_drm_fini(void)
>> +{
>> +     DBG("fini");
>> +     tilcdc_tfp410_fini();
>> +     platform_driver_unregister(&tilcdc_platform_driver);
>> +}
>> +
>> +module_init(tilcdc_drm_init);
>> +module_exit(tilcdc_drm_fini);
>> +
>> +MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
>> +MODULE_DESCRIPTION("TI LCD Controller DRM Driver");
>> +MODULE_LICENSE("GPL");
>
>
> --
> Ken ar c'henta? |             ** Breizh ha Linux atav! **
> Jef             |               http://moinejf.free.fr/

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 2/4] drm/i2c: nxp-tda998x (v2)
  2013-01-23  9:42     ` Jean-Francois Moine
@ 2013-01-23 13:25       ` Rob Clark
  -1 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-23 13:25 UTC (permalink / raw)
  To: Jean-Francois Moine; +Cc: dri-devel, linux-omap, linux-arm-kernel, patches

On Wed, Jan 23, 2013 at 3:42 AM, Jean-Francois Moine <moinejf@free.fr> wrote:
> On Tue, 22 Jan 2013 16:36:23 -0600
> Rob Clark <robdclark@gmail.com> wrote:
>
>> Driver for the NXP TDA998X i2c hdmi encoder slave.
>>
>> v1: original
>> v2: fix npix/nline programming
>>
>> Signed-off-by: Rob Clark <robdclark@gmail.com>
>> ---
>>  drivers/gpu/drm/i2c/Makefile      |   3 +
>>  drivers/gpu/drm/i2c/tda998x_drv.c | 908 ++++++++++++++++++++++++++++++++++++++
>>  2 files changed, 911 insertions(+)
>>  create mode 100644 drivers/gpu/drm/i2c/tda998x_drv.c
>         [snip]
>> diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
>> new file mode 100644
>> index 0000000..02054e8
>> --- /dev/null
>> +++ b/drivers/gpu/drm/i2c/tda998x_drv.c
>> @@ -0,0 +1,908 @@
>         [snip]
>> +static void __exit
>> +tda998x_exit(void)
>> +{
>> +     DBG("");
>> +     drm_i2c_encoder_unregister(&tda998x_driver);
>> +}
>> +
>> +MODULE_DESCRIPTION("NXP Semiconductors TDA998X TMDS transmitter driver");
>> +
>> +MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
>> +MODULE_DESCRIPTION("NXP Semiconductors TDA998X HDMI Encoder");
>> +MODULE_LICENSE("GPL");
>> +
>> +module_init(tda998x_init);
>> +module_exit(tda998x_exit);
>
> There are 2 MODULE_DESCRIPTION().

oh, whoops.. I'll fix that

BR,
-R

> --
> Ken ar c'hentañ |             ** Breizh ha Linux atav! **
> Jef             |               http://moinejf.free.fr/
--
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

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 2/4] drm/i2c: nxp-tda998x (v2)
@ 2013-01-23 13:25       ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-23 13:25 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Jan 23, 2013 at 3:42 AM, Jean-Francois Moine <moinejf@free.fr> wrote:
> On Tue, 22 Jan 2013 16:36:23 -0600
> Rob Clark <robdclark@gmail.com> wrote:
>
>> Driver for the NXP TDA998X i2c hdmi encoder slave.
>>
>> v1: original
>> v2: fix npix/nline programming
>>
>> Signed-off-by: Rob Clark <robdclark@gmail.com>
>> ---
>>  drivers/gpu/drm/i2c/Makefile      |   3 +
>>  drivers/gpu/drm/i2c/tda998x_drv.c | 908 ++++++++++++++++++++++++++++++++++++++
>>  2 files changed, 911 insertions(+)
>>  create mode 100644 drivers/gpu/drm/i2c/tda998x_drv.c
>         [snip]
>> diff --git a/drivers/gpu/drm/i2c/tda998x_drv.c b/drivers/gpu/drm/i2c/tda998x_drv.c
>> new file mode 100644
>> index 0000000..02054e8
>> --- /dev/null
>> +++ b/drivers/gpu/drm/i2c/tda998x_drv.c
>> @@ -0,0 +1,908 @@
>         [snip]
>> +static void __exit
>> +tda998x_exit(void)
>> +{
>> +     DBG("");
>> +     drm_i2c_encoder_unregister(&tda998x_driver);
>> +}
>> +
>> +MODULE_DESCRIPTION("NXP Semiconductors TDA998X TMDS transmitter driver");
>> +
>> +MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
>> +MODULE_DESCRIPTION("NXP Semiconductors TDA998X HDMI Encoder");
>> +MODULE_LICENSE("GPL");
>> +
>> +module_init(tda998x_init);
>> +module_exit(tda998x_exit);
>
> There are 2 MODULE_DESCRIPTION().

oh, whoops.. I'll fix that

BR,
-R

> --
> Ken ar c'henta? |             ** Breizh ha Linux atav! **
> Jef             |               http://moinejf.free.fr/

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
  2013-01-23 13:24       ` Rob Clark
@ 2013-01-23 13:36         ` Russell King - ARM Linux
  -1 siblings, 0 replies; 66+ messages in thread
From: Russell King - ARM Linux @ 2013-01-23 13:36 UTC (permalink / raw)
  To: Rob Clark
  Cc: Jean-Francois Moine, linux-omap, linux-arm-kernel, dri-devel, patches

On Wed, Jan 23, 2013 at 07:24:33AM -0600, Rob Clark wrote:
> On Wed, Jan 23, 2013 at 3:42 AM, Jean-Francois Moine <moinejf@free.fr> wrote:
> > Hi Rob,
> >
> > As I wanted to re-use your nxp-tda998x driver for the Marvell Dove SoC,
> > I had a look at your IT LCD driver. Comments below.
> 
> Just fyi, you can re-use the nxp-tda998x part independently of tilcdc
> (just in case that wasn't clear).

Great, this chip is also used on the cubox too.

The one thing I wonder is how you deal with the VSYNC/HSYNC polarities
that are provided to the TDA9988x chip.  On the cubox, I have to adjust
the mode parameters such that the polarities are fixed up thusly:

        adjusted->flags &= ~(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NHSYNC |
                             DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC |
                             DRM_MODE_FLAG_PCSYNC | DRM_MODE_FLAG_NCSYNC);

        /* The TDA19988 always requires negative VSYNC? */
        adjusted->flags |= DRM_MODE_FLAG_NVSYNC;

        /* The TDA19988 requires positive HSYNC on 1080p or 720p */
        if ((adjusted->hdisplay == 1920 && adjusted->vdisplay == 1080) ||
            (adjusted->hdisplay == 1280 && adjusted->vdisplay == 720))
                adjusted->flags |= DRM_MODE_FLAG_PHSYNC;
        else
                adjusted->flags |= DRM_MODE_FLAG_NHSYNC;

        return true;

for these resolutions to be displayed correctly.

Also, when the only output is the HDMI device, reporting the display
"disconnected" and without any modes seems to cause problems - I have to
report "unknown" status when there's nothing connected, and also provide
a default (720p) mode when no EDID is received.

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-23 13:36         ` Russell King - ARM Linux
  0 siblings, 0 replies; 66+ messages in thread
From: Russell King - ARM Linux @ 2013-01-23 13:36 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Jan 23, 2013 at 07:24:33AM -0600, Rob Clark wrote:
> On Wed, Jan 23, 2013 at 3:42 AM, Jean-Francois Moine <moinejf@free.fr> wrote:
> > Hi Rob,
> >
> > As I wanted to re-use your nxp-tda998x driver for the Marvell Dove SoC,
> > I had a look at your IT LCD driver. Comments below.
> 
> Just fyi, you can re-use the nxp-tda998x part independently of tilcdc
> (just in case that wasn't clear).

Great, this chip is also used on the cubox too.

The one thing I wonder is how you deal with the VSYNC/HSYNC polarities
that are provided to the TDA9988x chip.  On the cubox, I have to adjust
the mode parameters such that the polarities are fixed up thusly:

        adjusted->flags &= ~(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NHSYNC |
                             DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC |
                             DRM_MODE_FLAG_PCSYNC | DRM_MODE_FLAG_NCSYNC);

        /* The TDA19988 always requires negative VSYNC? */
        adjusted->flags |= DRM_MODE_FLAG_NVSYNC;

        /* The TDA19988 requires positive HSYNC on 1080p or 720p */
        if ((adjusted->hdisplay == 1920 && adjusted->vdisplay == 1080) ||
            (adjusted->hdisplay == 1280 && adjusted->vdisplay == 720))
                adjusted->flags |= DRM_MODE_FLAG_PHSYNC;
        else
                adjusted->flags |= DRM_MODE_FLAG_NHSYNC;

        return true;

for these resolutions to be displayed correctly.

Also, when the only output is the HDMI device, reporting the display
"disconnected" and without any modes seems to cause problems - I have to
report "unknown" status when there's nothing connected, and also provide
a default (720p) mode when no EDID is received.

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
  2013-01-23 13:36         ` Russell King - ARM Linux
@ 2013-01-23 14:13           ` Rob Clark
  -1 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-23 14:13 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: Jean-Francois Moine, linux-omap, linux-arm-kernel, dri-devel, patches

On Wed, Jan 23, 2013 at 7:36 AM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Wed, Jan 23, 2013 at 07:24:33AM -0600, Rob Clark wrote:
>> On Wed, Jan 23, 2013 at 3:42 AM, Jean-Francois Moine <moinejf@free.fr> wrote:
>> > Hi Rob,
>> >
>> > As I wanted to re-use your nxp-tda998x driver for the Marvell Dove SoC,
>> > I had a look at your IT LCD driver. Comments below.
>>
>> Just fyi, you can re-use the nxp-tda998x part independently of tilcdc
>> (just in case that wasn't clear).
>
> Great, this chip is also used on the cubox too.

cool, it would be great if other platforms could benefit from this as
well.. the out-of-tree nxp driver is just horrid ;-)

> The one thing I wonder is how you deal with the VSYNC/HSYNC polarities
> that are provided to the TDA9988x chip.  On the cubox, I have to adjust
> the mode parameters such that the polarities are fixed up thusly:
>
>         adjusted->flags &= ~(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NHSYNC |
>                              DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC |
>                              DRM_MODE_FLAG_PCSYNC | DRM_MODE_FLAG_NCSYNC);
>
>         /* The TDA19988 always requires negative VSYNC? */
>         adjusted->flags |= DRM_MODE_FLAG_NVSYNC;
>
>         /* The TDA19988 requires positive HSYNC on 1080p or 720p */
>         if ((adjusted->hdisplay == 1920 && adjusted->vdisplay == 1080) ||
>             (adjusted->hdisplay == 1280 && adjusted->vdisplay == 720))
>                 adjusted->flags |= DRM_MODE_FLAG_PHSYNC;
>         else
>                 adjusted->flags |= DRM_MODE_FLAG_NHSYNC;
>
>         return true;
>
> for these resolutions to be displayed correctly.

hmm, I didn't come across this.  Although 1080p seems to be a bit more
than what the little board I'm working on can drive.  I didn't have to
do any special fixup for 720p..

I wonder if you are having to do that with the nxp out of tree driver?
 Maybe it is related to how that driver it is configuring the hw?  It
would be interesting if you hit the same issue w/ the i2c encoder
slave version.

At any rate, if it turns out to be needed, we can add this in
tda998x_encoder_mode_fixup().

> Also, when the only output is the HDMI device, reporting the display
> "disconnected" and without any modes seems to cause problems - I have to
> report "unknown" status when there's nothing connected, and also provide
> a default (720p) mode when no EDID is received.

hmm, also did not run into any issues here on my end.  What sort of
problems did you hit?

BR,
-R

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-23 14:13           ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-23 14:13 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Jan 23, 2013 at 7:36 AM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Wed, Jan 23, 2013 at 07:24:33AM -0600, Rob Clark wrote:
>> On Wed, Jan 23, 2013 at 3:42 AM, Jean-Francois Moine <moinejf@free.fr> wrote:
>> > Hi Rob,
>> >
>> > As I wanted to re-use your nxp-tda998x driver for the Marvell Dove SoC,
>> > I had a look at your IT LCD driver. Comments below.
>>
>> Just fyi, you can re-use the nxp-tda998x part independently of tilcdc
>> (just in case that wasn't clear).
>
> Great, this chip is also used on the cubox too.

cool, it would be great if other platforms could benefit from this as
well.. the out-of-tree nxp driver is just horrid ;-)

> The one thing I wonder is how you deal with the VSYNC/HSYNC polarities
> that are provided to the TDA9988x chip.  On the cubox, I have to adjust
> the mode parameters such that the polarities are fixed up thusly:
>
>         adjusted->flags &= ~(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NHSYNC |
>                              DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC |
>                              DRM_MODE_FLAG_PCSYNC | DRM_MODE_FLAG_NCSYNC);
>
>         /* The TDA19988 always requires negative VSYNC? */
>         adjusted->flags |= DRM_MODE_FLAG_NVSYNC;
>
>         /* The TDA19988 requires positive HSYNC on 1080p or 720p */
>         if ((adjusted->hdisplay == 1920 && adjusted->vdisplay == 1080) ||
>             (adjusted->hdisplay == 1280 && adjusted->vdisplay == 720))
>                 adjusted->flags |= DRM_MODE_FLAG_PHSYNC;
>         else
>                 adjusted->flags |= DRM_MODE_FLAG_NHSYNC;
>
>         return true;
>
> for these resolutions to be displayed correctly.

hmm, I didn't come across this.  Although 1080p seems to be a bit more
than what the little board I'm working on can drive.  I didn't have to
do any special fixup for 720p..

I wonder if you are having to do that with the nxp out of tree driver?
 Maybe it is related to how that driver it is configuring the hw?  It
would be interesting if you hit the same issue w/ the i2c encoder
slave version.

At any rate, if it turns out to be needed, we can add this in
tda998x_encoder_mode_fixup().

> Also, when the only output is the HDMI device, reporting the display
> "disconnected" and without any modes seems to cause problems - I have to
> report "unknown" status when there's nothing connected, and also provide
> a default (720p) mode when no EDID is received.

hmm, also did not run into any issues here on my end.  What sort of
problems did you hit?

BR,
-R

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
  2013-01-23 14:13           ` Rob Clark
@ 2013-01-23 14:37             ` Rob Clark
  -1 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-23 14:37 UTC (permalink / raw)
  To: Russell King - ARM Linux
  Cc: Jean-Francois Moine, linux-omap, dri-devel, linux-arm-kernel, patches

On Wed, Jan 23, 2013 at 8:13 AM, Rob Clark <robdclark@gmail.com> wrote:
> On Wed, Jan 23, 2013 at 7:36 AM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
>> On Wed, Jan 23, 2013 at 07:24:33AM -0600, Rob Clark wrote:
>>> On Wed, Jan 23, 2013 at 3:42 AM, Jean-Francois Moine <moinejf@free.fr> wrote:
>>> > Hi Rob,
>>> >
>>> > As I wanted to re-use your nxp-tda998x driver for the Marvell Dove SoC,
>>> > I had a look at your IT LCD driver. Comments below.
>>>
>>> Just fyi, you can re-use the nxp-tda998x part independently of tilcdc
>>> (just in case that wasn't clear).
>>
>> Great, this chip is also used on the cubox too.
>
> cool, it would be great if other platforms could benefit from this as
> well.. the out-of-tree nxp driver is just horrid ;-)
>
>> The one thing I wonder is how you deal with the VSYNC/HSYNC polarities
>> that are provided to the TDA9988x chip.  On the cubox, I have to adjust
>> the mode parameters such that the polarities are fixed up thusly:
>>
>>         adjusted->flags &= ~(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NHSYNC |
>>                              DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC |
>>                              DRM_MODE_FLAG_PCSYNC | DRM_MODE_FLAG_NCSYNC);
>>
>>         /* The TDA19988 always requires negative VSYNC? */
>>         adjusted->flags |= DRM_MODE_FLAG_NVSYNC;
>>
>>         /* The TDA19988 requires positive HSYNC on 1080p or 720p */
>>         if ((adjusted->hdisplay == 1920 && adjusted->vdisplay == 1080) ||
>>             (adjusted->hdisplay == 1280 && adjusted->vdisplay == 720))
>>                 adjusted->flags |= DRM_MODE_FLAG_PHSYNC;
>>         else
>>                 adjusted->flags |= DRM_MODE_FLAG_NHSYNC;
>>
>>         return true;
>>
>> for these resolutions to be displayed correctly.
>
> hmm, I didn't come across this.  Although 1080p seems to be a bit more
> than what the little board I'm working on can drive.  I didn't have to
> do any special fixup for 720p..

one thought.. I haven't enabled any hdmi features like audio yet..
which could be a reason that I didn't hit some of these issues.  I'm
not really much of an audio/asoc expert so I'm not really sure how
this should hook in for audio stuff, but if someone out there with
some hw with a similar nxp encoder chip does have some better audio
experience, I'd be interested to work together on that.

BR,
-R

> I wonder if you are having to do that with the nxp out of tree driver?
>  Maybe it is related to how that driver it is configuring the hw?  It
> would be interesting if you hit the same issue w/ the i2c encoder
> slave version.
>
> At any rate, if it turns out to be needed, we can add this in
> tda998x_encoder_mode_fixup().
>
>> Also, when the only output is the HDMI device, reporting the display
>> "disconnected" and without any modes seems to cause problems - I have to
>> report "unknown" status when there's nothing connected, and also provide
>> a default (720p) mode when no EDID is received.
>
> hmm, also did not run into any issues here on my end.  What sort of
> problems did you hit?
>
> BR,
> -R

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-23 14:37             ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-23 14:37 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Jan 23, 2013 at 8:13 AM, Rob Clark <robdclark@gmail.com> wrote:
> On Wed, Jan 23, 2013 at 7:36 AM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
>> On Wed, Jan 23, 2013 at 07:24:33AM -0600, Rob Clark wrote:
>>> On Wed, Jan 23, 2013 at 3:42 AM, Jean-Francois Moine <moinejf@free.fr> wrote:
>>> > Hi Rob,
>>> >
>>> > As I wanted to re-use your nxp-tda998x driver for the Marvell Dove SoC,
>>> > I had a look at your IT LCD driver. Comments below.
>>>
>>> Just fyi, you can re-use the nxp-tda998x part independently of tilcdc
>>> (just in case that wasn't clear).
>>
>> Great, this chip is also used on the cubox too.
>
> cool, it would be great if other platforms could benefit from this as
> well.. the out-of-tree nxp driver is just horrid ;-)
>
>> The one thing I wonder is how you deal with the VSYNC/HSYNC polarities
>> that are provided to the TDA9988x chip.  On the cubox, I have to adjust
>> the mode parameters such that the polarities are fixed up thusly:
>>
>>         adjusted->flags &= ~(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NHSYNC |
>>                              DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC |
>>                              DRM_MODE_FLAG_PCSYNC | DRM_MODE_FLAG_NCSYNC);
>>
>>         /* The TDA19988 always requires negative VSYNC? */
>>         adjusted->flags |= DRM_MODE_FLAG_NVSYNC;
>>
>>         /* The TDA19988 requires positive HSYNC on 1080p or 720p */
>>         if ((adjusted->hdisplay == 1920 && adjusted->vdisplay == 1080) ||
>>             (adjusted->hdisplay == 1280 && adjusted->vdisplay == 720))
>>                 adjusted->flags |= DRM_MODE_FLAG_PHSYNC;
>>         else
>>                 adjusted->flags |= DRM_MODE_FLAG_NHSYNC;
>>
>>         return true;
>>
>> for these resolutions to be displayed correctly.
>
> hmm, I didn't come across this.  Although 1080p seems to be a bit more
> than what the little board I'm working on can drive.  I didn't have to
> do any special fixup for 720p..

one thought.. I haven't enabled any hdmi features like audio yet..
which could be a reason that I didn't hit some of these issues.  I'm
not really much of an audio/asoc expert so I'm not really sure how
this should hook in for audio stuff, but if someone out there with
some hw with a similar nxp encoder chip does have some better audio
experience, I'd be interested to work together on that.

BR,
-R

> I wonder if you are having to do that with the nxp out of tree driver?
>  Maybe it is related to how that driver it is configuring the hw?  It
> would be interesting if you hit the same issue w/ the i2c encoder
> slave version.
>
> At any rate, if it turns out to be needed, we can add this in
> tda998x_encoder_mode_fixup().
>
>> Also, when the only output is the HDMI device, reporting the display
>> "disconnected" and without any modes seems to cause problems - I have to
>> report "unknown" status when there's nothing connected, and also provide
>> a default (720p) mode when no EDID is received.
>
> hmm, also did not run into any issues here on my end.  What sort of
> problems did you hit?
>
> BR,
> -R

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 2/4] drm/i2c: nxp-tda998x (v2)
  2013-01-22 22:36   ` Rob Clark
@ 2013-01-24 11:57     ` Daniel Vetter
  -1 siblings, 0 replies; 66+ messages in thread
From: Daniel Vetter @ 2013-01-24 11:57 UTC (permalink / raw)
  To: Rob Clark; +Cc: dri-devel, linux-omap, linux-arm-kernel, patches

On Tue, Jan 22, 2013 at 04:36:23PM -0600, Rob Clark wrote:
> Driver for the NXP TDA998X i2c hdmi encoder slave.
> 
> v1: original
> v2: fix npix/nline programming
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>

Just one bikeshed, otherwise

Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>

[cut]

> +static void
> +reg_set(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
> +{
> +	reg_write(encoder, reg, reg_read(encoder, reg) | val);
> +}
> +
> +static void
> +reg_clear(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
> +{
> +	reg_write(encoder, reg, reg_read(encoder, reg) & ~val);
> +}

What about drivers/base/regmap? I haven't looked to closely yet and never
used it in code, but there's a presentation [1] and it sounds like it
provides some nice (and more important standardized) helper stuff for
debug, tracing, ...

Since encoder slave drivers tend to be utterly boring register bashing and
we expect tons of time, I think high levels of standardization would be
really useful. Care to look into this a bit?

Cheers, Daniel

1: http://free-electrons.com/blog/fosdem2012-videos/
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 2/4] drm/i2c: nxp-tda998x (v2)
@ 2013-01-24 11:57     ` Daniel Vetter
  0 siblings, 0 replies; 66+ messages in thread
From: Daniel Vetter @ 2013-01-24 11:57 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Jan 22, 2013 at 04:36:23PM -0600, Rob Clark wrote:
> Driver for the NXP TDA998X i2c hdmi encoder slave.
> 
> v1: original
> v2: fix npix/nline programming
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>

Just one bikeshed, otherwise

Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>

[cut]

> +static void
> +reg_set(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
> +{
> +	reg_write(encoder, reg, reg_read(encoder, reg) | val);
> +}
> +
> +static void
> +reg_clear(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
> +{
> +	reg_write(encoder, reg, reg_read(encoder, reg) & ~val);
> +}

What about drivers/base/regmap? I haven't looked to closely yet and never
used it in code, but there's a presentation [1] and it sounds like it
provides some nice (and more important standardized) helper stuff for
debug, tracing, ...

Since encoder slave drivers tend to be utterly boring register bashing and
we expect tons of time, I think high levels of standardization would be
really useful. Care to look into this a bit?

Cheers, Daniel

1: http://free-electrons.com/blog/fosdem2012-videos/
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 3/4] drm/tilcdc: add encoder slave
  2013-01-22 22:36   ` Rob Clark
@ 2013-01-24 12:43     ` Daniel Vetter
  -1 siblings, 0 replies; 66+ messages in thread
From: Daniel Vetter @ 2013-01-24 12:43 UTC (permalink / raw)
  To: Rob Clark; +Cc: dri-devel, linux-omap, linux-arm-kernel, patches

On Tue, Jan 22, 2013 at 04:36:24PM -0600, Rob Clark wrote:
> Add output panel driver for i2c encoder slaves.
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>

A few questions below, otherwise

Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>
> ---
>  drivers/gpu/drm/tilcdc/Kconfig        |  12 ++
>  drivers/gpu/drm/tilcdc/Makefile       |   1 +
>  drivers/gpu/drm/tilcdc/tilcdc_drv.c   |   5 +-
>  drivers/gpu/drm/tilcdc/tilcdc_slave.c | 380 ++++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_slave.h |  26 +++
>  5 files changed, 423 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.h
> 
> diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig
> index ee9b592..99beca2 100644
> --- a/drivers/gpu/drm/tilcdc/Kconfig
> +++ b/drivers/gpu/drm/tilcdc/Kconfig
> @@ -8,3 +8,15 @@ config DRM_TILCDC
>  	  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.
> +
> +menu "I2C encoder or helper chips"
> +	depends on DRM && DRM_KMS_HELPER && I2C
> +
> +config DRM_I2C_NXP_TDA998X
> +	tristate "NXP Semiconductors TDA998X HDMI encoder"
> +	default m if DRM_TILCDC
> +	help
> +	  Support for NXP Semiconductors TDA998X HDMI encoders.
> +
> +endmenu
> +

Shouldn't that hunk be in patch 2?

> diff --git a/drivers/gpu/drm/tilcdc/Makefile b/drivers/gpu/drm/tilcdc/Makefile
> index 1359cc2..aa9097e 100644
> --- a/drivers/gpu/drm/tilcdc/Makefile
> +++ b/drivers/gpu/drm/tilcdc/Makefile
> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm -Werror
>  tilcdc-y := \
>  	tilcdc_crtc.o \
>  	tilcdc_tfp410.o \
> +	tilcdc_slave.o \
>  	tilcdc_drv.o
>  
>  obj-$(CONFIG_DRM_TILCDC)	+= tilcdc.o
> diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
> index cf1fddc..ca76dbe 100644
> --- a/drivers/gpu/drm/tilcdc/tilcdc_drv.c
> +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
> @@ -20,6 +20,7 @@
>  #include "tilcdc_drv.h"
>  #include "tilcdc_regs.h"
>  #include "tilcdc_tfp410.h"
> +#include "tilcdc_slave.h"
>  
>  #include "drm_fb_helper.h"
>  
> @@ -587,6 +588,7 @@ static int __init tilcdc_drm_init(void)
>  {
>  	DBG("init");
>  	tilcdc_tfp410_init();
> +	tilcdc_slave_init();
>  	return platform_driver_register(&tilcdc_platform_driver);
>  }
>  
> @@ -594,10 +596,11 @@ static void __exit tilcdc_drm_fini(void)
>  {
>  	DBG("fini");
>  	tilcdc_tfp410_fini();
> +	tilcdc_slave_fini();
>  	platform_driver_unregister(&tilcdc_platform_driver);
>  }
>  
> -module_init(tilcdc_drm_init);
> +late_initcall(tilcdc_drm_init);
>  module_exit(tilcdc_drm_fini);
>  
>  MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
> diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave.c b/drivers/gpu/drm/tilcdc/tilcdc_slave.c
> new file mode 100644
> index 0000000..b6f3e63
> --- /dev/null
> +++ b/drivers/gpu/drm/tilcdc/tilcdc_slave.c
> @@ -0,0 +1,380 @@
> +/*
> + * 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/pinctrl/pinmux.h>
> +#include <linux/pinctrl/consumer.h>
> +#include <drm/drm_encoder_slave.h>
> +
> +#include "tilcdc_drv.h"
> +
> +struct slave_module {
> +	struct tilcdc_module base;
> +	struct i2c_adapter *i2c;
> +};
> +#define to_slave_module(x) container_of(x, struct slave_module, base)
> +
> +static const struct tilcdc_panel_info slave_info = {
> +		.min_bpp                = 16,
> +		.max_bpp                = 16,
> +		.bpp                    = 16,
> +		.ac_bias                = 255,
> +		.ac_bias_intrpt         = 0,
> +		.dma_burst_sz           = 16,
> +		.fdd                    = 0x80,
> +		.tft_alt_mode           = 0,
> +		.stn_565_mode           = 0,
> +		.mono_8bit_mode         = 0,
> +		.sync_edge              = 0,
> +		.sync_ctrl              = 1,
> +		.raster_order           = 0,
> +};
> +
> +
> +/*
> + * Encoder:
> + */
> +
> +struct slave_encoder {
> +	struct drm_encoder_slave base;
> +	struct slave_module *mod;
> +};
> +#define to_slave_encoder(x) container_of(to_encoder_slave(x), struct slave_encoder, base)

Since you have a 1:1:1 relationship between module/drm_encoder, the
drm_encoder_slave and the connector I'd just smash this all into one
struct. Pure bikeshed though ;-)

> +
> +static inline struct drm_encoder_slave_funcs *
> +get_slave_funcs(struct drm_encoder *enc)
> +{
> +	return to_encoder_slave(enc)->slave_funcs;
> +}
> +
> +static void slave_encoder_destroy(struct drm_encoder *encoder)
> +{
> +	struct slave_encoder *slave_encoder = to_slave_encoder(encoder);
> +	if (get_slave_funcs(encoder))
> +		get_slave_funcs(encoder)->destroy(encoder);
> +	drm_encoder_cleanup(encoder);
> +	kfree(slave_encoder);
> +}
> +
> +static void slave_encoder_prepare(struct drm_encoder *encoder)
> +{
> +	drm_i2c_encoder_prepare(encoder);
> +	tilcdc_crtc_set_panel_info(encoder->crtc, &slave_info);
> +}
> +
> +static const struct drm_encoder_funcs slave_encoder_funcs = {
> +		.destroy        = slave_encoder_destroy,
> +};
> +
> +static const struct drm_encoder_helper_funcs slave_encoder_helper_funcs = {
> +		.dpms           = drm_i2c_encoder_dpms,
> +		.mode_fixup     = drm_i2c_encoder_mode_fixup,
> +		.prepare        = slave_encoder_prepare,
> +		.commit         = drm_i2c_encoder_commit,
> +		.mode_set       = drm_i2c_encoder_mode_set,
> +		.save           = drm_i2c_encoder_save,
> +		.restore        = drm_i2c_encoder_restore,
> +};
> +
> +static const struct i2c_board_info info = {
> +		I2C_BOARD_INFO("tda998x", 0x70)
> +};
> +
> +static struct drm_encoder *slave_encoder_create(struct drm_device *dev,
> +		struct slave_module *mod)
> +{
> +	struct slave_encoder *slave_encoder;
> +	struct drm_encoder *encoder;
> +	int ret;
> +
> +	slave_encoder = kzalloc(sizeof(*slave_encoder), GFP_KERNEL);
> +	if (!slave_encoder) {
> +		dev_err(dev->dev, "allocation failed\n");
> +		return NULL;
> +	}
> +
> +	slave_encoder->mod = mod;
> +
> +	encoder = &slave_encoder->base.base;
> +	encoder->possible_crtcs = 1;
> +
> +	ret = drm_encoder_init(dev, encoder, &slave_encoder_funcs,
> +			DRM_MODE_ENCODER_LVDS);

DRM_MODE_ENCODER_TMDS? Although I guess adding a new kind of
multi-function encoder type would make more sense and also useful in other
places. E.g. i915-sdvo/dvo just set meaningless types for multi-function
encoders (i.e. more than one connector on the same output), namely the
type which matches the connector last initalized.

> +	if (ret)
> +		goto fail;
> +
> +	drm_encoder_helper_add(encoder, &slave_encoder_helper_funcs);
> +
> +	ret = drm_i2c_encoder_init(dev, to_encoder_slave(encoder), mod->i2c, &info);
> +	if (ret)
> +		goto fail;
> +
> +	return encoder;
> +
> +fail:
> +	slave_encoder_destroy(encoder);
> +	return NULL;
> +}
> +
> +/*
> + * Connector:
> + */
> +
> +struct slave_connector {
> +	struct drm_connector base;
> +
> +	struct drm_encoder *encoder;  /* our connected encoder */
> +	struct slave_module *mod;
> +};
> +#define to_slave_connector(x) container_of(x, struct slave_connector, base)
> +
> +static void slave_connector_destroy(struct drm_connector *connector)
> +{
> +	struct slave_connector *slave_connector = to_slave_connector(connector);
> +	drm_connector_cleanup(connector);
> +	kfree(slave_connector);
> +}
> +
> +static enum drm_connector_status slave_connector_detect(
> +		struct drm_connector *connector,
> +		bool force)
> +{
> +	struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
> +	return get_slave_funcs(encoder)->detect(encoder, connector);
> +}
> +
> +static int slave_connector_get_modes(struct drm_connector *connector)
> +{
> +	struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
> +	return get_slave_funcs(encoder)->get_modes(encoder, connector);
> +}
> +
> +static int slave_connector_mode_valid(struct drm_connector *connector,
> +		  struct drm_display_mode *mode)
> +{
> +	struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
> +	struct tilcdc_drm_private *priv = connector->dev->dev_private;
> +	int ret;
> +
> +	ret = tilcdc_crtc_mode_valid(priv->crtc, mode);
> +	if (ret != MODE_OK)
> +		return ret;
> +
> +	return get_slave_funcs(encoder)->mode_valid(encoder, mode);
> +}
> +
> +static struct drm_encoder *slave_connector_best_encoder(
> +		struct drm_connector *connector)
> +{
> +	struct slave_connector *slave_connector = to_slave_connector(connector);
> +	return slave_connector->encoder;
> +}
> +
> +static int slave_connector_set_property(struct drm_connector *connector,
> +		struct drm_property *property, uint64_t value)
> +{
> +	struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
> +	return get_slave_funcs(encoder)->set_property(encoder,
> +			connector, property, value);
> +}
> +
> +static const struct drm_connector_funcs slave_connector_funcs = {
> +	.destroy            = slave_connector_destroy,
> +	.dpms               = drm_helper_connector_dpms,
> +	.detect             = slave_connector_detect,
> +	.fill_modes         = drm_helper_probe_single_connector_modes,
> +	.set_property       = slave_connector_set_property,
> +};
> +
> +static const struct drm_connector_helper_funcs slave_connector_helper_funcs = {
> +	.get_modes          = slave_connector_get_modes,
> +	.mode_valid         = slave_connector_mode_valid,
> +	.best_encoder       = slave_connector_best_encoder,
> +};
> +
> +static struct drm_connector *slave_connector_create(struct drm_device *dev,
> +		struct slave_module *mod, struct drm_encoder *encoder)
> +{
> +	struct slave_connector *slave_connector;
> +	struct drm_connector *connector;
> +	int ret;
> +
> +	slave_connector = kzalloc(sizeof(*slave_connector), GFP_KERNEL);
> +	if (!slave_connector) {
> +		dev_err(dev->dev, "allocation failed\n");
> +		return NULL;
> +	}
> +
> +	slave_connector->encoder = encoder;
> +	slave_connector->mod = mod;
> +
> +	connector = &slave_connector->base;
> +
> +	drm_connector_init(dev, connector, &slave_connector_funcs,
> +			DRM_MODE_CONNECTOR_HDMIA);

Shouldn't we get the connector type from the drm_encoder_slave somehow?
Works here for now, so just food for thought for future encoder slave
refactorings and infrastructure work.

> +	drm_connector_helper_add(connector, &slave_connector_helper_funcs);
> +
> +	connector->polled = DRM_CONNECTOR_POLL_CONNECT |
> +			DRM_CONNECTOR_POLL_DISCONNECT;
> +
> +	connector->interlace_allowed = 0;
> +	connector->doublescan_allowed = 0;
> +
> +	get_slave_funcs(encoder)->create_resources(encoder, connector);
> +
> +	ret = drm_mode_connector_attach_encoder(connector, encoder);
> +	if (ret)
> +		goto fail;
> +
> +	drm_sysfs_connector_add(connector);
> +
> +	return connector;
> +
> +fail:
> +	slave_connector_destroy(connector);
> +	return NULL;
> +}
> +
> +/*
> + * Module:
> + */
> +
> +static int slave_modeset_init(struct tilcdc_module *mod, struct drm_device *dev)
> +{
> +	struct slave_module *slave_mod = to_slave_module(mod);
> +	struct tilcdc_drm_private *priv = dev->dev_private;
> +	struct drm_encoder *encoder;
> +	struct drm_connector *connector;
> +
> +	encoder = slave_encoder_create(dev, slave_mod);
> +	if (!encoder)
> +		return -ENOMEM;
> +
> +	connector = slave_connector_create(dev, slave_mod, encoder);
> +	if (!connector)
> +		return -ENOMEM;
> +
> +	priv->encoders[priv->num_encoders++] = encoder;
> +	priv->connectors[priv->num_connectors++] = connector;
> +
> +	return 0;
> +}
> +
> +static void slave_destroy(struct tilcdc_module *mod)
> +{
> +	struct slave_module *slave_mod = to_slave_module(mod);
> +
> +	tilcdc_module_cleanup(mod);
> +	kfree(slave_mod);
> +}
> +
> +static const struct tilcdc_module_ops slave_module_ops = {
> +		.modeset_init = slave_modeset_init,
> +		.destroy = slave_destroy,
> +};
> +
> +/*
> + * Device:
> + */
> +
> +static struct of_device_id slave_of_match[];
> +
> +static int slave_probe(struct platform_device *pdev)
> +{
> +	struct device_node *node = pdev->dev.of_node;
> +	struct device_node *i2c_node;
> +	struct slave_module *slave_mod;
> +	struct tilcdc_module *mod;
> +	struct pinctrl *pinctrl;
> +	uint32_t i2c_phandle;
> +	int ret = -EINVAL;
> +
> +	/* bail out early if no DT data: */
> +	if (!node) {
> +		dev_err(&pdev->dev, "device-tree data is missing\n");
> +		return -ENXIO;
> +	}
> +
> +	slave_mod = kzalloc(sizeof(*slave_mod), GFP_KERNEL);
> +	if (!slave_mod)
> +		return -ENOMEM;
> +
> +	mod = &slave_mod->base;
> +
> +	tilcdc_module_init(mod, "slave", &slave_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;
> +	}
> +
> +	slave_mod->i2c = of_find_i2c_adapter_by_node(i2c_node);
> +	if (!slave_mod->i2c) {
> +		dev_err(&pdev->dev, "could not get i2c\n");
> +		goto fail;
> +	}
> +
> +	of_node_put(i2c_node);
> +
> +	return 0;
> +
> +fail:
> +	slave_destroy(mod);
> +	return ret;
> +}
> +
> +static int slave_remove(struct platform_device *pdev)
> +{
> +	return 0;
> +}
> +
> +static struct of_device_id slave_of_match[] = {
> +		{ .compatible = "tilcdc,slave", },
> +		{ },
> +};
> +MODULE_DEVICE_TABLE(of, slave_of_match);
> +
> +struct platform_driver slave_driver = {
> +	.probe = slave_probe,
> +	.remove = slave_remove,
> +	.driver = {
> +		.owner = THIS_MODULE,
> +		.name = "slave",
> +		.of_match_table = slave_of_match,
> +	},
> +};

No idea how this devicetree matching stuff is supposed to work, but I
guess this needs to be updated in the devictree docs like the base match.

> +
> +int __init tilcdc_slave_init(void)
> +{
> +	return platform_driver_register(&slave_driver);
> +}
> +
> +void __exit tilcdc_slave_fini(void)
> +{
> +	platform_driver_unregister(&slave_driver);
> +}
> diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave.h b/drivers/gpu/drm/tilcdc/tilcdc_slave.h
> new file mode 100644
> index 0000000..2f85048
> --- /dev/null
> +++ b/drivers/gpu/drm/tilcdc/tilcdc_slave.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 __TILCDC_SLAVE_H__
> +#define __TILCDC_SLAVE_H__
> +
> +/* sub-module for i2c slave encoder output */
> +
> +int tilcdc_slave_init(void);
> +void tilcdc_slave_fini(void);
> +
> +#endif /* __TILCDC_SLAVE_H__ */
> -- 
> 1.8.1
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 3/4] drm/tilcdc: add encoder slave
@ 2013-01-24 12:43     ` Daniel Vetter
  0 siblings, 0 replies; 66+ messages in thread
From: Daniel Vetter @ 2013-01-24 12:43 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Jan 22, 2013 at 04:36:24PM -0600, Rob Clark wrote:
> Add output panel driver for i2c encoder slaves.
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>

A few questions below, otherwise

Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>
> ---
>  drivers/gpu/drm/tilcdc/Kconfig        |  12 ++
>  drivers/gpu/drm/tilcdc/Makefile       |   1 +
>  drivers/gpu/drm/tilcdc/tilcdc_drv.c   |   5 +-
>  drivers/gpu/drm/tilcdc/tilcdc_slave.c | 380 ++++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/tilcdc/tilcdc_slave.h |  26 +++
>  5 files changed, 423 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.c
>  create mode 100644 drivers/gpu/drm/tilcdc/tilcdc_slave.h
> 
> diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig
> index ee9b592..99beca2 100644
> --- a/drivers/gpu/drm/tilcdc/Kconfig
> +++ b/drivers/gpu/drm/tilcdc/Kconfig
> @@ -8,3 +8,15 @@ config DRM_TILCDC
>  	  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.
> +
> +menu "I2C encoder or helper chips"
> +	depends on DRM && DRM_KMS_HELPER && I2C
> +
> +config DRM_I2C_NXP_TDA998X
> +	tristate "NXP Semiconductors TDA998X HDMI encoder"
> +	default m if DRM_TILCDC
> +	help
> +	  Support for NXP Semiconductors TDA998X HDMI encoders.
> +
> +endmenu
> +

Shouldn't that hunk be in patch 2?

> diff --git a/drivers/gpu/drm/tilcdc/Makefile b/drivers/gpu/drm/tilcdc/Makefile
> index 1359cc2..aa9097e 100644
> --- a/drivers/gpu/drm/tilcdc/Makefile
> +++ b/drivers/gpu/drm/tilcdc/Makefile
> @@ -3,6 +3,7 @@ ccflags-y := -Iinclude/drm -Werror
>  tilcdc-y := \
>  	tilcdc_crtc.o \
>  	tilcdc_tfp410.o \
> +	tilcdc_slave.o \
>  	tilcdc_drv.o
>  
>  obj-$(CONFIG_DRM_TILCDC)	+= tilcdc.o
> diff --git a/drivers/gpu/drm/tilcdc/tilcdc_drv.c b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
> index cf1fddc..ca76dbe 100644
> --- a/drivers/gpu/drm/tilcdc/tilcdc_drv.c
> +++ b/drivers/gpu/drm/tilcdc/tilcdc_drv.c
> @@ -20,6 +20,7 @@
>  #include "tilcdc_drv.h"
>  #include "tilcdc_regs.h"
>  #include "tilcdc_tfp410.h"
> +#include "tilcdc_slave.h"
>  
>  #include "drm_fb_helper.h"
>  
> @@ -587,6 +588,7 @@ static int __init tilcdc_drm_init(void)
>  {
>  	DBG("init");
>  	tilcdc_tfp410_init();
> +	tilcdc_slave_init();
>  	return platform_driver_register(&tilcdc_platform_driver);
>  }
>  
> @@ -594,10 +596,11 @@ static void __exit tilcdc_drm_fini(void)
>  {
>  	DBG("fini");
>  	tilcdc_tfp410_fini();
> +	tilcdc_slave_fini();
>  	platform_driver_unregister(&tilcdc_platform_driver);
>  }
>  
> -module_init(tilcdc_drm_init);
> +late_initcall(tilcdc_drm_init);
>  module_exit(tilcdc_drm_fini);
>  
>  MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
> diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave.c b/drivers/gpu/drm/tilcdc/tilcdc_slave.c
> new file mode 100644
> index 0000000..b6f3e63
> --- /dev/null
> +++ b/drivers/gpu/drm/tilcdc/tilcdc_slave.c
> @@ -0,0 +1,380 @@
> +/*
> + * 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/pinctrl/pinmux.h>
> +#include <linux/pinctrl/consumer.h>
> +#include <drm/drm_encoder_slave.h>
> +
> +#include "tilcdc_drv.h"
> +
> +struct slave_module {
> +	struct tilcdc_module base;
> +	struct i2c_adapter *i2c;
> +};
> +#define to_slave_module(x) container_of(x, struct slave_module, base)
> +
> +static const struct tilcdc_panel_info slave_info = {
> +		.min_bpp                = 16,
> +		.max_bpp                = 16,
> +		.bpp                    = 16,
> +		.ac_bias                = 255,
> +		.ac_bias_intrpt         = 0,
> +		.dma_burst_sz           = 16,
> +		.fdd                    = 0x80,
> +		.tft_alt_mode           = 0,
> +		.stn_565_mode           = 0,
> +		.mono_8bit_mode         = 0,
> +		.sync_edge              = 0,
> +		.sync_ctrl              = 1,
> +		.raster_order           = 0,
> +};
> +
> +
> +/*
> + * Encoder:
> + */
> +
> +struct slave_encoder {
> +	struct drm_encoder_slave base;
> +	struct slave_module *mod;
> +};
> +#define to_slave_encoder(x) container_of(to_encoder_slave(x), struct slave_encoder, base)

Since you have a 1:1:1 relationship between module/drm_encoder, the
drm_encoder_slave and the connector I'd just smash this all into one
struct. Pure bikeshed though ;-)

> +
> +static inline struct drm_encoder_slave_funcs *
> +get_slave_funcs(struct drm_encoder *enc)
> +{
> +	return to_encoder_slave(enc)->slave_funcs;
> +}
> +
> +static void slave_encoder_destroy(struct drm_encoder *encoder)
> +{
> +	struct slave_encoder *slave_encoder = to_slave_encoder(encoder);
> +	if (get_slave_funcs(encoder))
> +		get_slave_funcs(encoder)->destroy(encoder);
> +	drm_encoder_cleanup(encoder);
> +	kfree(slave_encoder);
> +}
> +
> +static void slave_encoder_prepare(struct drm_encoder *encoder)
> +{
> +	drm_i2c_encoder_prepare(encoder);
> +	tilcdc_crtc_set_panel_info(encoder->crtc, &slave_info);
> +}
> +
> +static const struct drm_encoder_funcs slave_encoder_funcs = {
> +		.destroy        = slave_encoder_destroy,
> +};
> +
> +static const struct drm_encoder_helper_funcs slave_encoder_helper_funcs = {
> +		.dpms           = drm_i2c_encoder_dpms,
> +		.mode_fixup     = drm_i2c_encoder_mode_fixup,
> +		.prepare        = slave_encoder_prepare,
> +		.commit         = drm_i2c_encoder_commit,
> +		.mode_set       = drm_i2c_encoder_mode_set,
> +		.save           = drm_i2c_encoder_save,
> +		.restore        = drm_i2c_encoder_restore,
> +};
> +
> +static const struct i2c_board_info info = {
> +		I2C_BOARD_INFO("tda998x", 0x70)
> +};
> +
> +static struct drm_encoder *slave_encoder_create(struct drm_device *dev,
> +		struct slave_module *mod)
> +{
> +	struct slave_encoder *slave_encoder;
> +	struct drm_encoder *encoder;
> +	int ret;
> +
> +	slave_encoder = kzalloc(sizeof(*slave_encoder), GFP_KERNEL);
> +	if (!slave_encoder) {
> +		dev_err(dev->dev, "allocation failed\n");
> +		return NULL;
> +	}
> +
> +	slave_encoder->mod = mod;
> +
> +	encoder = &slave_encoder->base.base;
> +	encoder->possible_crtcs = 1;
> +
> +	ret = drm_encoder_init(dev, encoder, &slave_encoder_funcs,
> +			DRM_MODE_ENCODER_LVDS);

DRM_MODE_ENCODER_TMDS? Although I guess adding a new kind of
multi-function encoder type would make more sense and also useful in other
places. E.g. i915-sdvo/dvo just set meaningless types for multi-function
encoders (i.e. more than one connector on the same output), namely the
type which matches the connector last initalized.

> +	if (ret)
> +		goto fail;
> +
> +	drm_encoder_helper_add(encoder, &slave_encoder_helper_funcs);
> +
> +	ret = drm_i2c_encoder_init(dev, to_encoder_slave(encoder), mod->i2c, &info);
> +	if (ret)
> +		goto fail;
> +
> +	return encoder;
> +
> +fail:
> +	slave_encoder_destroy(encoder);
> +	return NULL;
> +}
> +
> +/*
> + * Connector:
> + */
> +
> +struct slave_connector {
> +	struct drm_connector base;
> +
> +	struct drm_encoder *encoder;  /* our connected encoder */
> +	struct slave_module *mod;
> +};
> +#define to_slave_connector(x) container_of(x, struct slave_connector, base)
> +
> +static void slave_connector_destroy(struct drm_connector *connector)
> +{
> +	struct slave_connector *slave_connector = to_slave_connector(connector);
> +	drm_connector_cleanup(connector);
> +	kfree(slave_connector);
> +}
> +
> +static enum drm_connector_status slave_connector_detect(
> +		struct drm_connector *connector,
> +		bool force)
> +{
> +	struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
> +	return get_slave_funcs(encoder)->detect(encoder, connector);
> +}
> +
> +static int slave_connector_get_modes(struct drm_connector *connector)
> +{
> +	struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
> +	return get_slave_funcs(encoder)->get_modes(encoder, connector);
> +}
> +
> +static int slave_connector_mode_valid(struct drm_connector *connector,
> +		  struct drm_display_mode *mode)
> +{
> +	struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
> +	struct tilcdc_drm_private *priv = connector->dev->dev_private;
> +	int ret;
> +
> +	ret = tilcdc_crtc_mode_valid(priv->crtc, mode);
> +	if (ret != MODE_OK)
> +		return ret;
> +
> +	return get_slave_funcs(encoder)->mode_valid(encoder, mode);
> +}
> +
> +static struct drm_encoder *slave_connector_best_encoder(
> +		struct drm_connector *connector)
> +{
> +	struct slave_connector *slave_connector = to_slave_connector(connector);
> +	return slave_connector->encoder;
> +}
> +
> +static int slave_connector_set_property(struct drm_connector *connector,
> +		struct drm_property *property, uint64_t value)
> +{
> +	struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
> +	return get_slave_funcs(encoder)->set_property(encoder,
> +			connector, property, value);
> +}
> +
> +static const struct drm_connector_funcs slave_connector_funcs = {
> +	.destroy            = slave_connector_destroy,
> +	.dpms               = drm_helper_connector_dpms,
> +	.detect             = slave_connector_detect,
> +	.fill_modes         = drm_helper_probe_single_connector_modes,
> +	.set_property       = slave_connector_set_property,
> +};
> +
> +static const struct drm_connector_helper_funcs slave_connector_helper_funcs = {
> +	.get_modes          = slave_connector_get_modes,
> +	.mode_valid         = slave_connector_mode_valid,
> +	.best_encoder       = slave_connector_best_encoder,
> +};
> +
> +static struct drm_connector *slave_connector_create(struct drm_device *dev,
> +		struct slave_module *mod, struct drm_encoder *encoder)
> +{
> +	struct slave_connector *slave_connector;
> +	struct drm_connector *connector;
> +	int ret;
> +
> +	slave_connector = kzalloc(sizeof(*slave_connector), GFP_KERNEL);
> +	if (!slave_connector) {
> +		dev_err(dev->dev, "allocation failed\n");
> +		return NULL;
> +	}
> +
> +	slave_connector->encoder = encoder;
> +	slave_connector->mod = mod;
> +
> +	connector = &slave_connector->base;
> +
> +	drm_connector_init(dev, connector, &slave_connector_funcs,
> +			DRM_MODE_CONNECTOR_HDMIA);

Shouldn't we get the connector type from the drm_encoder_slave somehow?
Works here for now, so just food for thought for future encoder slave
refactorings and infrastructure work.

> +	drm_connector_helper_add(connector, &slave_connector_helper_funcs);
> +
> +	connector->polled = DRM_CONNECTOR_POLL_CONNECT |
> +			DRM_CONNECTOR_POLL_DISCONNECT;
> +
> +	connector->interlace_allowed = 0;
> +	connector->doublescan_allowed = 0;
> +
> +	get_slave_funcs(encoder)->create_resources(encoder, connector);
> +
> +	ret = drm_mode_connector_attach_encoder(connector, encoder);
> +	if (ret)
> +		goto fail;
> +
> +	drm_sysfs_connector_add(connector);
> +
> +	return connector;
> +
> +fail:
> +	slave_connector_destroy(connector);
> +	return NULL;
> +}
> +
> +/*
> + * Module:
> + */
> +
> +static int slave_modeset_init(struct tilcdc_module *mod, struct drm_device *dev)
> +{
> +	struct slave_module *slave_mod = to_slave_module(mod);
> +	struct tilcdc_drm_private *priv = dev->dev_private;
> +	struct drm_encoder *encoder;
> +	struct drm_connector *connector;
> +
> +	encoder = slave_encoder_create(dev, slave_mod);
> +	if (!encoder)
> +		return -ENOMEM;
> +
> +	connector = slave_connector_create(dev, slave_mod, encoder);
> +	if (!connector)
> +		return -ENOMEM;
> +
> +	priv->encoders[priv->num_encoders++] = encoder;
> +	priv->connectors[priv->num_connectors++] = connector;
> +
> +	return 0;
> +}
> +
> +static void slave_destroy(struct tilcdc_module *mod)
> +{
> +	struct slave_module *slave_mod = to_slave_module(mod);
> +
> +	tilcdc_module_cleanup(mod);
> +	kfree(slave_mod);
> +}
> +
> +static const struct tilcdc_module_ops slave_module_ops = {
> +		.modeset_init = slave_modeset_init,
> +		.destroy = slave_destroy,
> +};
> +
> +/*
> + * Device:
> + */
> +
> +static struct of_device_id slave_of_match[];
> +
> +static int slave_probe(struct platform_device *pdev)
> +{
> +	struct device_node *node = pdev->dev.of_node;
> +	struct device_node *i2c_node;
> +	struct slave_module *slave_mod;
> +	struct tilcdc_module *mod;
> +	struct pinctrl *pinctrl;
> +	uint32_t i2c_phandle;
> +	int ret = -EINVAL;
> +
> +	/* bail out early if no DT data: */
> +	if (!node) {
> +		dev_err(&pdev->dev, "device-tree data is missing\n");
> +		return -ENXIO;
> +	}
> +
> +	slave_mod = kzalloc(sizeof(*slave_mod), GFP_KERNEL);
> +	if (!slave_mod)
> +		return -ENOMEM;
> +
> +	mod = &slave_mod->base;
> +
> +	tilcdc_module_init(mod, "slave", &slave_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;
> +	}
> +
> +	slave_mod->i2c = of_find_i2c_adapter_by_node(i2c_node);
> +	if (!slave_mod->i2c) {
> +		dev_err(&pdev->dev, "could not get i2c\n");
> +		goto fail;
> +	}
> +
> +	of_node_put(i2c_node);
> +
> +	return 0;
> +
> +fail:
> +	slave_destroy(mod);
> +	return ret;
> +}
> +
> +static int slave_remove(struct platform_device *pdev)
> +{
> +	return 0;
> +}
> +
> +static struct of_device_id slave_of_match[] = {
> +		{ .compatible = "tilcdc,slave", },
> +		{ },
> +};
> +MODULE_DEVICE_TABLE(of, slave_of_match);
> +
> +struct platform_driver slave_driver = {
> +	.probe = slave_probe,
> +	.remove = slave_remove,
> +	.driver = {
> +		.owner = THIS_MODULE,
> +		.name = "slave",
> +		.of_match_table = slave_of_match,
> +	},
> +};

No idea how this devicetree matching stuff is supposed to work, but I
guess this needs to be updated in the devictree docs like the base match.

> +
> +int __init tilcdc_slave_init(void)
> +{
> +	return platform_driver_register(&slave_driver);
> +}
> +
> +void __exit tilcdc_slave_fini(void)
> +{
> +	platform_driver_unregister(&slave_driver);
> +}
> diff --git a/drivers/gpu/drm/tilcdc/tilcdc_slave.h b/drivers/gpu/drm/tilcdc/tilcdc_slave.h
> new file mode 100644
> index 0000000..2f85048
> --- /dev/null
> +++ b/drivers/gpu/drm/tilcdc/tilcdc_slave.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 __TILCDC_SLAVE_H__
> +#define __TILCDC_SLAVE_H__
> +
> +/* sub-module for i2c slave encoder output */
> +
> +int tilcdc_slave_init(void);
> +void tilcdc_slave_fini(void);
> +
> +#endif /* __TILCDC_SLAVE_H__ */
> -- 
> 1.8.1
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel at lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/dri-devel

-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 3/4] drm/tilcdc: add encoder slave
  2013-01-22 22:36   ` Rob Clark
@ 2013-01-24 13:01     ` Daniel Vetter
  -1 siblings, 0 replies; 66+ messages in thread
From: Daniel Vetter @ 2013-01-24 13:01 UTC (permalink / raw)
  To: Rob Clark; +Cc: dri-devel, linux-omap, linux-arm-kernel, patches

On Tue, Jan 22, 2013 at 04:36:24PM -0600, Rob Clark wrote:
> Add output panel driver for i2c encoder slaves.
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>

Found some more stuff ...

[cut]

> +static const struct drm_encoder_helper_funcs slave_encoder_helper_funcs = {
> +		.dpms           = drm_i2c_encoder_dpms,
> +		.mode_fixup     = drm_i2c_encoder_mode_fixup,
> +		.prepare        = slave_encoder_prepare,
> +		.commit         = drm_i2c_encoder_commit,
> +		.mode_set       = drm_i2c_encoder_mode_set,
> +		.save           = drm_i2c_encoder_save,
> +		.restore        = drm_i2c_encoder_restore,
> +};

I couldn't find these wrappers anywhere ...

> +
> +static const struct i2c_board_info info = {
> +		I2C_BOARD_INFO("tda998x", 0x70)
> +};

Shouldn't there be some of/devicetree thing to tell us which one to load?
-Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 3/4] drm/tilcdc: add encoder slave
@ 2013-01-24 13:01     ` Daniel Vetter
  0 siblings, 0 replies; 66+ messages in thread
From: Daniel Vetter @ 2013-01-24 13:01 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Jan 22, 2013 at 04:36:24PM -0600, Rob Clark wrote:
> Add output panel driver for i2c encoder slaves.
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>

Found some more stuff ...

[cut]

> +static const struct drm_encoder_helper_funcs slave_encoder_helper_funcs = {
> +		.dpms           = drm_i2c_encoder_dpms,
> +		.mode_fixup     = drm_i2c_encoder_mode_fixup,
> +		.prepare        = slave_encoder_prepare,
> +		.commit         = drm_i2c_encoder_commit,
> +		.mode_set       = drm_i2c_encoder_mode_set,
> +		.save           = drm_i2c_encoder_save,
> +		.restore        = drm_i2c_encoder_restore,
> +};

I couldn't find these wrappers anywhere ...

> +
> +static const struct i2c_board_info info = {
> +		I2C_BOARD_INFO("tda998x", 0x70)
> +};

Shouldn't there be some of/devicetree thing to tell us which one to load?
-Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 4/4] drm/tilcdc: add support for LCD panels (v4)
  2013-01-22 22:36   ` Rob Clark
@ 2013-01-24 13:08     ` Daniel Vetter
  -1 siblings, 0 replies; 66+ messages in thread
From: Daniel Vetter @ 2013-01-24 13:08 UTC (permalink / raw)
  To: Rob Clark; +Cc: dri-devel, linux-omap, linux-arm-kernel, patches

On Tue, Jan 22, 2013 at 04:36:25PM -0600, Rob Clark wrote:
> Add an output panel driver for LCD panels.  Tested with LCD3 cape on
> beaglebone.
> 
> v1: original
> v2: s/of_find_node_by_name()/of_get_child_by_name()/ from Pantelis
>     Antoniou
> v3: add backlight support
> v4: rebase to latest of video timing helpers
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>

So given that I'm utterly lacking clue about all things of (which seems to
be where all the magic in this patch lies) I'm just gonna ask a few funny
questions.

[cut]

> +static int panel_connector_get_modes(struct drm_connector *connector)
> +{
> +	struct drm_device *dev = connector->dev;
> +	struct panel_connector *panel_connector = to_panel_connector(connector);
> +	struct display_timings *timings = panel_connector->mod->timings;
> +	int i;
> +
> +	for (i = 0; i < timings->num_timings; i++) {
> +		struct drm_display_mode *mode = drm_mode_create(dev);
> +		struct videomode vm;
> +
> +		if (videomode_from_timing(timings, &vm, i))
> +			break;
> +
> +		drm_display_mode_from_videomode(&vm, mode);

Why do we need to jump through the intermediate videomode thing here? Is
that a deficiency of the of/videomode stuff?

[cut]

> +	ret |= of_property_read_u32(info_np, "ac-bias", &info->ac_bias);
> +	ret |= of_property_read_u32(info_np, "ac-bias-intrpt", &info->ac_bias_intrpt);
> +	ret |= of_property_read_u32(info_np, "dma-burst-sz", &info->dma_burst_sz);
> +	ret |= of_property_read_u32(info_np, "bpp", &info->bpp);
> +	ret |= of_property_read_u32(info_np, "fdd", &info->fdd);
> +	ret |= of_property_read_u32(info_np, "sync-edge", &info->sync_edge);
> +	ret |= of_property_read_u32(info_np, "sync-ctrl", &info->sync_ctrl);
> +	ret |= of_property_read_u32(info_np, "raster-order", &info->raster_order);
> +	ret |= of_property_read_u32(info_np, "fifo-th", &info->fifo_th);

Shouldn't these values all be documented somewhere in the devictree docs?
Or are they somewhat standardized?

> +
> +	/* optional: */
> +	info->tft_alt_mode      = of_property_read_bool(info_np, "tft-alt-mode");
> +	info->stn_565_mode      = of_property_read_bool(info_np, "stn-565-mode");
> +	info->mono_8bit_mode    = of_property_read_bool(info_np, "mono-8bit-mode");
> +	info->invert_pxl_clk    = of_property_read_bool(info_np, "invert-pxl-clk");
> +
> +	if (of_property_read_u32(info_np, "max-bpp", &info->max_bpp))
> +		info->max_bpp = info->bpp;
> +	if (of_property_read_u32(info_np, "min-bpp", &info->min_bpp))
> +		info->min_bpp = info->bpp;
> +
> +	if (ret) {
> +		pr_err("%s: error reading panel-info properties\n", __func__);
> +		kfree(info);
> +		return NULL;
> +	}
> +
> +	return info;
> +}
> +
> +static struct of_device_id panel_of_match[];
> +
> +static int panel_probe(struct platform_device *pdev)
> +{
> +	struct device_node *node = pdev->dev.of_node;
> +	struct panel_module *panel_mod;
> +	struct tilcdc_module *mod;
> +	struct pinctrl *pinctrl;
> +	int ret = -EINVAL;
> +
> +
> +	/* bail out early if no DT data: */
> +	if (!node) {
> +		dev_err(&pdev->dev, "device-tree data is missing\n");
> +		return -ENXIO;
> +	}
> +
> +	panel_mod = kzalloc(sizeof(*panel_mod), GFP_KERNEL);
> +	if (!panel_mod)
> +		return -ENOMEM;
> +
> +	mod = &panel_mod->base;
> +
> +	tilcdc_module_init(mod, "panel", &panel_module_ops);
> +
> +	pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
> +	if (IS_ERR(pinctrl))
> +		dev_warn(&pdev->dev, "pins are not configured\n");
> +
> +
> +	panel_mod->timings = of_get_display_timings(node);
> +	if (!panel_mod->timings) {
> +		dev_err(&pdev->dev, "could not get panel timings\n");
> +		goto fail;
> +	}
> +
> +	panel_mod->info = of_get_panel_info(node);
> +	if (!panel_mod->info) {
> +		dev_err(&pdev->dev, "could not get panel info\n");
> +		goto fail;
> +	}
> +
> +	panel_mod->backlight = of_find_backlight_by_node(node);

If this _really_ works that easily, I'll have of-envy for the rest of my
life :(

/me hates the real-world abomination called Intel backlight handling


Cheers, Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 4/4] drm/tilcdc: add support for LCD panels (v4)
@ 2013-01-24 13:08     ` Daniel Vetter
  0 siblings, 0 replies; 66+ messages in thread
From: Daniel Vetter @ 2013-01-24 13:08 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Jan 22, 2013 at 04:36:25PM -0600, Rob Clark wrote:
> Add an output panel driver for LCD panels.  Tested with LCD3 cape on
> beaglebone.
> 
> v1: original
> v2: s/of_find_node_by_name()/of_get_child_by_name()/ from Pantelis
>     Antoniou
> v3: add backlight support
> v4: rebase to latest of video timing helpers
> 
> Signed-off-by: Rob Clark <robdclark@gmail.com>

So given that I'm utterly lacking clue about all things of (which seems to
be where all the magic in this patch lies) I'm just gonna ask a few funny
questions.

[cut]

> +static int panel_connector_get_modes(struct drm_connector *connector)
> +{
> +	struct drm_device *dev = connector->dev;
> +	struct panel_connector *panel_connector = to_panel_connector(connector);
> +	struct display_timings *timings = panel_connector->mod->timings;
> +	int i;
> +
> +	for (i = 0; i < timings->num_timings; i++) {
> +		struct drm_display_mode *mode = drm_mode_create(dev);
> +		struct videomode vm;
> +
> +		if (videomode_from_timing(timings, &vm, i))
> +			break;
> +
> +		drm_display_mode_from_videomode(&vm, mode);

Why do we need to jump through the intermediate videomode thing here? Is
that a deficiency of the of/videomode stuff?

[cut]

> +	ret |= of_property_read_u32(info_np, "ac-bias", &info->ac_bias);
> +	ret |= of_property_read_u32(info_np, "ac-bias-intrpt", &info->ac_bias_intrpt);
> +	ret |= of_property_read_u32(info_np, "dma-burst-sz", &info->dma_burst_sz);
> +	ret |= of_property_read_u32(info_np, "bpp", &info->bpp);
> +	ret |= of_property_read_u32(info_np, "fdd", &info->fdd);
> +	ret |= of_property_read_u32(info_np, "sync-edge", &info->sync_edge);
> +	ret |= of_property_read_u32(info_np, "sync-ctrl", &info->sync_ctrl);
> +	ret |= of_property_read_u32(info_np, "raster-order", &info->raster_order);
> +	ret |= of_property_read_u32(info_np, "fifo-th", &info->fifo_th);

Shouldn't these values all be documented somewhere in the devictree docs?
Or are they somewhat standardized?

> +
> +	/* optional: */
> +	info->tft_alt_mode      = of_property_read_bool(info_np, "tft-alt-mode");
> +	info->stn_565_mode      = of_property_read_bool(info_np, "stn-565-mode");
> +	info->mono_8bit_mode    = of_property_read_bool(info_np, "mono-8bit-mode");
> +	info->invert_pxl_clk    = of_property_read_bool(info_np, "invert-pxl-clk");
> +
> +	if (of_property_read_u32(info_np, "max-bpp", &info->max_bpp))
> +		info->max_bpp = info->bpp;
> +	if (of_property_read_u32(info_np, "min-bpp", &info->min_bpp))
> +		info->min_bpp = info->bpp;
> +
> +	if (ret) {
> +		pr_err("%s: error reading panel-info properties\n", __func__);
> +		kfree(info);
> +		return NULL;
> +	}
> +
> +	return info;
> +}
> +
> +static struct of_device_id panel_of_match[];
> +
> +static int panel_probe(struct platform_device *pdev)
> +{
> +	struct device_node *node = pdev->dev.of_node;
> +	struct panel_module *panel_mod;
> +	struct tilcdc_module *mod;
> +	struct pinctrl *pinctrl;
> +	int ret = -EINVAL;
> +
> +
> +	/* bail out early if no DT data: */
> +	if (!node) {
> +		dev_err(&pdev->dev, "device-tree data is missing\n");
> +		return -ENXIO;
> +	}
> +
> +	panel_mod = kzalloc(sizeof(*panel_mod), GFP_KERNEL);
> +	if (!panel_mod)
> +		return -ENOMEM;
> +
> +	mod = &panel_mod->base;
> +
> +	tilcdc_module_init(mod, "panel", &panel_module_ops);
> +
> +	pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
> +	if (IS_ERR(pinctrl))
> +		dev_warn(&pdev->dev, "pins are not configured\n");
> +
> +
> +	panel_mod->timings = of_get_display_timings(node);
> +	if (!panel_mod->timings) {
> +		dev_err(&pdev->dev, "could not get panel timings\n");
> +		goto fail;
> +	}
> +
> +	panel_mod->info = of_get_panel_info(node);
> +	if (!panel_mod->info) {
> +		dev_err(&pdev->dev, "could not get panel info\n");
> +		goto fail;
> +	}
> +
> +	panel_mod->backlight = of_find_backlight_by_node(node);

If this _really_ works that easily, I'll have of-envy for the rest of my
life :(

/me hates the real-world abomination called Intel backlight handling


Cheers, Daniel
-- 
Daniel Vetter
Software Engineer, Intel Corporation
+41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 2/4] drm/i2c: nxp-tda998x (v2)
  2013-01-24 11:57     ` Daniel Vetter
@ 2013-01-24 14:10       ` Rob Clark
  -1 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-24 14:10 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: dri-devel, linux-omap, linux-arm-kernel, patches

On Thu, Jan 24, 2013 at 5:57 AM, Daniel Vetter <daniel@ffwll.ch> wrote:
> On Tue, Jan 22, 2013 at 04:36:23PM -0600, Rob Clark wrote:
>> Driver for the NXP TDA998X i2c hdmi encoder slave.
>>
>> v1: original
>> v2: fix npix/nline programming
>>
>> Signed-off-by: Rob Clark <robdclark@gmail.com>
>
> Just one bikeshed, otherwise
>
> Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>
>
> [cut]
>
>> +static void
>> +reg_set(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
>> +{
>> +     reg_write(encoder, reg, reg_read(encoder, reg) | val);
>> +}
>> +
>> +static void
>> +reg_clear(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
>> +{
>> +     reg_write(encoder, reg, reg_read(encoder, reg) & ~val);
>> +}
>
> What about drivers/base/regmap? I haven't looked to closely yet and never
> used it in code, but there's a presentation [1] and it sounds like it
> provides some nice (and more important standardized) helper stuff for
> debug, tracing, ...
>
> Since encoder slave drivers tend to be utterly boring register bashing and
> we expect tons of time, I think high levels of standardization would be
> really useful. Care to look into this a bit?

I did look at regmap.. what convinced me against using it was that if
you don't use cached mode, it ends up writing the page selector
register for every read/write.  And I don't have enough actual
documentation about this nxp part to know the reset values of all the
registers in order to use caching.

BR,
-R

> Cheers, Daniel
>
> 1: http://free-electrons.com/blog/fosdem2012-videos/
> --
> Daniel Vetter
> Software Engineer, Intel Corporation
> +41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 2/4] drm/i2c: nxp-tda998x (v2)
@ 2013-01-24 14:10       ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-24 14:10 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, Jan 24, 2013 at 5:57 AM, Daniel Vetter <daniel@ffwll.ch> wrote:
> On Tue, Jan 22, 2013 at 04:36:23PM -0600, Rob Clark wrote:
>> Driver for the NXP TDA998X i2c hdmi encoder slave.
>>
>> v1: original
>> v2: fix npix/nline programming
>>
>> Signed-off-by: Rob Clark <robdclark@gmail.com>
>
> Just one bikeshed, otherwise
>
> Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>
>
> [cut]
>
>> +static void
>> +reg_set(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
>> +{
>> +     reg_write(encoder, reg, reg_read(encoder, reg) | val);
>> +}
>> +
>> +static void
>> +reg_clear(struct drm_encoder *encoder, uint16_t reg, uint8_t val)
>> +{
>> +     reg_write(encoder, reg, reg_read(encoder, reg) & ~val);
>> +}
>
> What about drivers/base/regmap? I haven't looked to closely yet and never
> used it in code, but there's a presentation [1] and it sounds like it
> provides some nice (and more important standardized) helper stuff for
> debug, tracing, ...
>
> Since encoder slave drivers tend to be utterly boring register bashing and
> we expect tons of time, I think high levels of standardization would be
> really useful. Care to look into this a bit?

I did look at regmap.. what convinced me against using it was that if
you don't use cached mode, it ends up writing the page selector
register for every read/write.  And I don't have enough actual
documentation about this nxp part to know the reset values of all the
registers in order to use caching.

BR,
-R

> Cheers, Daniel
>
> 1: http://free-electrons.com/blog/fosdem2012-videos/
> --
> Daniel Vetter
> Software Engineer, Intel Corporation
> +41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 3/4] drm/tilcdc: add encoder slave
  2013-01-24 12:43     ` Daniel Vetter
@ 2013-01-24 14:26       ` Rob Clark
  -1 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-24 14:26 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: dri-devel, linux-omap, linux-arm-kernel, patches

On Thu, Jan 24, 2013 at 6:43 AM, Daniel Vetter <daniel@ffwll.ch> wrote:
> On Tue, Jan 22, 2013 at 04:36:24PM -0600, Rob Clark wrote:
>> Add output panel driver for i2c encoder slaves.
>>
>> Signed-off-by: Rob Clark <robdclark@gmail.com>
>
> A few questions below, otherwise
>
> Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>
>> ---

[snip]

>> diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig
>> index ee9b592..99beca2 100644
>> --- a/drivers/gpu/drm/tilcdc/Kconfig
>> +++ b/drivers/gpu/drm/tilcdc/Kconfig
>> @@ -8,3 +8,15 @@ config DRM_TILCDC
>>         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.
>> +
>> +menu "I2C encoder or helper chips"
>> +     depends on DRM && DRM_KMS_HELPER && I2C
>> +
>> +config DRM_I2C_NXP_TDA998X
>> +     tristate "NXP Semiconductors TDA998X HDMI encoder"
>> +     default m if DRM_TILCDC
>> +     help
>> +       Support for NXP Semiconductors TDA998X HDMI encoders.
>> +
>> +endmenu
>> +
>
> Shouldn't that hunk be in patch 2?

yeah, probably.. I just copied how it was done in nouveau, but I think
probably the Kconfig for the i2c encoder slaves should be moved into
drivers/gpu/drm/i2c

[snip]

>> +/*
>> + * Encoder:
>> + */
>> +
>> +struct slave_encoder {
>> +     struct drm_encoder_slave base;
>> +     struct slave_module *mod;
>> +};
>> +#define to_slave_encoder(x) container_of(to_encoder_slave(x), struct slave_encoder, base)
>
> Since you have a 1:1:1 relationship between module/drm_encoder, the
> drm_encoder_slave and the connector I'd just smash this all into one
> struct. Pure bikeshed though ;-)

yeah, but drm_encoder_slave is coming from drm core

[snip]

>> +static struct drm_encoder *slave_encoder_create(struct drm_device *dev,
>> +             struct slave_module *mod)
>> +{
>> +     struct slave_encoder *slave_encoder;
>> +     struct drm_encoder *encoder;
>> +     int ret;
>> +
>> +     slave_encoder = kzalloc(sizeof(*slave_encoder), GFP_KERNEL);
>> +     if (!slave_encoder) {
>> +             dev_err(dev->dev, "allocation failed\n");
>> +             return NULL;
>> +     }
>> +
>> +     slave_encoder->mod = mod;
>> +
>> +     encoder = &slave_encoder->base.base;
>> +     encoder->possible_crtcs = 1;
>> +
>> +     ret = drm_encoder_init(dev, encoder, &slave_encoder_funcs,
>> +                     DRM_MODE_ENCODER_LVDS);
>
> DRM_MODE_ENCODER_TMDS? Although I guess adding a new kind of
> multi-function encoder type would make more sense and also useful in other
> places. E.g. i915-sdvo/dvo just set meaningless types for multi-function
> encoders (i.e. more than one connector on the same output), namely the
> type which matches the connector last initalized.

I suppose TDMS makes more sense.. perhaps getting both this and
connector type from the encoder-slave would make the most sense, but I
can change it to TDMS for now

[snip]

>> +static struct drm_connector *slave_connector_create(struct drm_device *dev,
>> +             struct slave_module *mod, struct drm_encoder *encoder)
>> +{
>> +     struct slave_connector *slave_connector;
>> +     struct drm_connector *connector;
>> +     int ret;
>> +
>> +     slave_connector = kzalloc(sizeof(*slave_connector), GFP_KERNEL);
>> +     if (!slave_connector) {
>> +             dev_err(dev->dev, "allocation failed\n");
>> +             return NULL;
>> +     }
>> +
>> +     slave_connector->encoder = encoder;
>> +     slave_connector->mod = mod;
>> +
>> +     connector = &slave_connector->base;
>> +
>> +     drm_connector_init(dev, connector, &slave_connector_funcs,
>> +                     DRM_MODE_CONNECTOR_HDMIA);
>
> Shouldn't we get the connector type from the drm_encoder_slave somehow?
> Works here for now, so just food for thought for future encoder slave
> refactorings and infrastructure work.

yeah, getting it from the encoder slave makes the most sense

[snip]

>> +static struct of_device_id slave_of_match[] = {
>> +             { .compatible = "tilcdc,slave", },
>> +             { },
>> +};
>> +MODULE_DEVICE_TABLE(of, slave_of_match);
>> +
>> +struct platform_driver slave_driver = {
>> +     .probe = slave_probe,
>> +     .remove = slave_remove,
>> +     .driver = {
>> +             .owner = THIS_MODULE,
>> +             .name = "slave",
>> +             .of_match_table = slave_of_match,
>> +     },
>> +};
>
> No idea how this devicetree matching stuff is supposed to work, but I
> guess this needs to be updated in the devictree docs like the base match.

yeah, I didn't realize previously about the DT bindings docs, so I
need to look into that

BR,
-R

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 3/4] drm/tilcdc: add encoder slave
@ 2013-01-24 14:26       ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-24 14:26 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, Jan 24, 2013 at 6:43 AM, Daniel Vetter <daniel@ffwll.ch> wrote:
> On Tue, Jan 22, 2013 at 04:36:24PM -0600, Rob Clark wrote:
>> Add output panel driver for i2c encoder slaves.
>>
>> Signed-off-by: Rob Clark <robdclark@gmail.com>
>
> A few questions below, otherwise
>
> Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch>
>> ---

[snip]

>> diff --git a/drivers/gpu/drm/tilcdc/Kconfig b/drivers/gpu/drm/tilcdc/Kconfig
>> index ee9b592..99beca2 100644
>> --- a/drivers/gpu/drm/tilcdc/Kconfig
>> +++ b/drivers/gpu/drm/tilcdc/Kconfig
>> @@ -8,3 +8,15 @@ config DRM_TILCDC
>>         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.
>> +
>> +menu "I2C encoder or helper chips"
>> +     depends on DRM && DRM_KMS_HELPER && I2C
>> +
>> +config DRM_I2C_NXP_TDA998X
>> +     tristate "NXP Semiconductors TDA998X HDMI encoder"
>> +     default m if DRM_TILCDC
>> +     help
>> +       Support for NXP Semiconductors TDA998X HDMI encoders.
>> +
>> +endmenu
>> +
>
> Shouldn't that hunk be in patch 2?

yeah, probably.. I just copied how it was done in nouveau, but I think
probably the Kconfig for the i2c encoder slaves should be moved into
drivers/gpu/drm/i2c

[snip]

>> +/*
>> + * Encoder:
>> + */
>> +
>> +struct slave_encoder {
>> +     struct drm_encoder_slave base;
>> +     struct slave_module *mod;
>> +};
>> +#define to_slave_encoder(x) container_of(to_encoder_slave(x), struct slave_encoder, base)
>
> Since you have a 1:1:1 relationship between module/drm_encoder, the
> drm_encoder_slave and the connector I'd just smash this all into one
> struct. Pure bikeshed though ;-)

yeah, but drm_encoder_slave is coming from drm core

[snip]

>> +static struct drm_encoder *slave_encoder_create(struct drm_device *dev,
>> +             struct slave_module *mod)
>> +{
>> +     struct slave_encoder *slave_encoder;
>> +     struct drm_encoder *encoder;
>> +     int ret;
>> +
>> +     slave_encoder = kzalloc(sizeof(*slave_encoder), GFP_KERNEL);
>> +     if (!slave_encoder) {
>> +             dev_err(dev->dev, "allocation failed\n");
>> +             return NULL;
>> +     }
>> +
>> +     slave_encoder->mod = mod;
>> +
>> +     encoder = &slave_encoder->base.base;
>> +     encoder->possible_crtcs = 1;
>> +
>> +     ret = drm_encoder_init(dev, encoder, &slave_encoder_funcs,
>> +                     DRM_MODE_ENCODER_LVDS);
>
> DRM_MODE_ENCODER_TMDS? Although I guess adding a new kind of
> multi-function encoder type would make more sense and also useful in other
> places. E.g. i915-sdvo/dvo just set meaningless types for multi-function
> encoders (i.e. more than one connector on the same output), namely the
> type which matches the connector last initalized.

I suppose TDMS makes more sense.. perhaps getting both this and
connector type from the encoder-slave would make the most sense, but I
can change it to TDMS for now

[snip]

>> +static struct drm_connector *slave_connector_create(struct drm_device *dev,
>> +             struct slave_module *mod, struct drm_encoder *encoder)
>> +{
>> +     struct slave_connector *slave_connector;
>> +     struct drm_connector *connector;
>> +     int ret;
>> +
>> +     slave_connector = kzalloc(sizeof(*slave_connector), GFP_KERNEL);
>> +     if (!slave_connector) {
>> +             dev_err(dev->dev, "allocation failed\n");
>> +             return NULL;
>> +     }
>> +
>> +     slave_connector->encoder = encoder;
>> +     slave_connector->mod = mod;
>> +
>> +     connector = &slave_connector->base;
>> +
>> +     drm_connector_init(dev, connector, &slave_connector_funcs,
>> +                     DRM_MODE_CONNECTOR_HDMIA);
>
> Shouldn't we get the connector type from the drm_encoder_slave somehow?
> Works here for now, so just food for thought for future encoder slave
> refactorings and infrastructure work.

yeah, getting it from the encoder slave makes the most sense

[snip]

>> +static struct of_device_id slave_of_match[] = {
>> +             { .compatible = "tilcdc,slave", },
>> +             { },
>> +};
>> +MODULE_DEVICE_TABLE(of, slave_of_match);
>> +
>> +struct platform_driver slave_driver = {
>> +     .probe = slave_probe,
>> +     .remove = slave_remove,
>> +     .driver = {
>> +             .owner = THIS_MODULE,
>> +             .name = "slave",
>> +             .of_match_table = slave_of_match,
>> +     },
>> +};
>
> No idea how this devicetree matching stuff is supposed to work, but I
> guess this needs to be updated in the devictree docs like the base match.

yeah, I didn't realize previously about the DT bindings docs, so I
need to look into that

BR,
-R

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 3/4] drm/tilcdc: add encoder slave
  2013-01-24 13:01     ` Daniel Vetter
@ 2013-01-24 14:31       ` Rob Clark
  -1 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-24 14:31 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: dri-devel, linux-omap, linux-arm-kernel, patches

On Thu, Jan 24, 2013 at 7:01 AM, Daniel Vetter <daniel@ffwll.ch> wrote:
> On Tue, Jan 22, 2013 at 04:36:24PM -0600, Rob Clark wrote:
>> Add output panel driver for i2c encoder slaves.
>>
>> Signed-off-by: Rob Clark <robdclark@gmail.com>
>
> Found some more stuff ...
>
> [cut]
>
>> +static const struct drm_encoder_helper_funcs slave_encoder_helper_funcs = {
>> +             .dpms           = drm_i2c_encoder_dpms,
>> +             .mode_fixup     = drm_i2c_encoder_mode_fixup,
>> +             .prepare        = slave_encoder_prepare,
>> +             .commit         = drm_i2c_encoder_commit,
>> +             .mode_set       = drm_i2c_encoder_mode_set,
>> +             .save           = drm_i2c_encoder_save,
>> +             .restore        = drm_i2c_encoder_restore,
>> +};
>
> I couldn't find these wrappers anywhere ...

this is in one of the dependent patches (also on my tilcdc-next branch):

https://patchwork.kernel.org/patch/1950971/

I also cleaned up nouveau to use 'em:

https://patchwork.kernel.org/patch/1951051/

>> +
>> +static const struct i2c_board_info info = {
>> +             I2C_BOARD_INFO("tda998x", 0x70)
>> +};
>
> Shouldn't there be some of/devicetree thing to tell us which one to load?

There are limited options for the i2c addresses, and all the boards
I've seen which have this part are at 0x70 and 0x34 for CEC.  Probably
the CEC address needs to come from a config struct or maybe the i2c
encoder just gets passed a 'struct device_node *'.  I'm not really
sure the best way to handle that to share the encoder between DT and
non-DT platforms, and since I didn't really need the configurability
yet, I just left that for now and decided we could figure out how to
handle that as the need arose.

BR,
-R

> -Daniel
> --
> Daniel Vetter
> Software Engineer, Intel Corporation
> +41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 3/4] drm/tilcdc: add encoder slave
@ 2013-01-24 14:31       ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-24 14:31 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, Jan 24, 2013 at 7:01 AM, Daniel Vetter <daniel@ffwll.ch> wrote:
> On Tue, Jan 22, 2013 at 04:36:24PM -0600, Rob Clark wrote:
>> Add output panel driver for i2c encoder slaves.
>>
>> Signed-off-by: Rob Clark <robdclark@gmail.com>
>
> Found some more stuff ...
>
> [cut]
>
>> +static const struct drm_encoder_helper_funcs slave_encoder_helper_funcs = {
>> +             .dpms           = drm_i2c_encoder_dpms,
>> +             .mode_fixup     = drm_i2c_encoder_mode_fixup,
>> +             .prepare        = slave_encoder_prepare,
>> +             .commit         = drm_i2c_encoder_commit,
>> +             .mode_set       = drm_i2c_encoder_mode_set,
>> +             .save           = drm_i2c_encoder_save,
>> +             .restore        = drm_i2c_encoder_restore,
>> +};
>
> I couldn't find these wrappers anywhere ...

this is in one of the dependent patches (also on my tilcdc-next branch):

https://patchwork.kernel.org/patch/1950971/

I also cleaned up nouveau to use 'em:

https://patchwork.kernel.org/patch/1951051/

>> +
>> +static const struct i2c_board_info info = {
>> +             I2C_BOARD_INFO("tda998x", 0x70)
>> +};
>
> Shouldn't there be some of/devicetree thing to tell us which one to load?

There are limited options for the i2c addresses, and all the boards
I've seen which have this part are at 0x70 and 0x34 for CEC.  Probably
the CEC address needs to come from a config struct or maybe the i2c
encoder just gets passed a 'struct device_node *'.  I'm not really
sure the best way to handle that to share the encoder between DT and
non-DT platforms, and since I didn't really need the configurability
yet, I just left that for now and decided we could figure out how to
handle that as the need arose.

BR,
-R

> -Daniel
> --
> Daniel Vetter
> Software Engineer, Intel Corporation
> +41 (0) 79 365 57 48 - http://blog.ffwll.ch

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 4/4] drm/tilcdc: add support for LCD panels (v4)
  2013-01-24 13:08     ` Daniel Vetter
@ 2013-01-24 14:40       ` Rob Clark
  -1 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-24 14:40 UTC (permalink / raw)
  To: Daniel Vetter; +Cc: dri-devel, linux-omap, linux-arm-kernel, patches

On Thu, Jan 24, 2013 at 7:08 AM, Daniel Vetter <daniel@ffwll.ch> wrote:
> On Tue, Jan 22, 2013 at 04:36:25PM -0600, Rob Clark wrote:
>> Add an output panel driver for LCD panels.  Tested with LCD3 cape on
>> beaglebone.
>>
>> v1: original
>> v2: s/of_find_node_by_name()/of_get_child_by_name()/ from Pantelis
>>     Antoniou
>> v3: add backlight support
>> v4: rebase to latest of video timing helpers
>>
>> Signed-off-by: Rob Clark <robdclark@gmail.com>
>
> So given that I'm utterly lacking clue about all things of (which seems to
> be where all the magic in this patch lies) I'm just gonna ask a few funny
> questions.
>
> [cut]
>
>> +static int panel_connector_get_modes(struct drm_connector *connector)
>> +{
>> +     struct drm_device *dev = connector->dev;
>> +     struct panel_connector *panel_connector = to_panel_connector(connector);
>> +     struct display_timings *timings = panel_connector->mod->timings;
>> +     int i;
>> +
>> +     for (i = 0; i < timings->num_timings; i++) {
>> +             struct drm_display_mode *mode = drm_mode_create(dev);
>> +             struct videomode vm;
>> +
>> +             if (videomode_from_timing(timings, &vm, i))
>> +                     break;
>> +
>> +             drm_display_mode_from_videomode(&vm, mode);
>
> Why do we need to jump through the intermediate videomode thing here? Is
> that a deficiency of the of/videomode stuff?

there is a helper that cut through the intermediate videomode
structure, although it assumed you are doing that at the point where
you still have the 'struct device_node *' (in the probe code) which
didn't really fit well for how I had structured things.  So I just
skipped it.  I guess I could have gone straight to array of
drm_display_mode in the probe call, and then copied them here in
get_modes() but this just seemed a bit easier.

> [cut]
>
>> +     ret |= of_property_read_u32(info_np, "ac-bias", &info->ac_bias);
>> +     ret |= of_property_read_u32(info_np, "ac-bias-intrpt", &info->ac_bias_intrpt);
>> +     ret |= of_property_read_u32(info_np, "dma-burst-sz", &info->dma_burst_sz);
>> +     ret |= of_property_read_u32(info_np, "bpp", &info->bpp);
>> +     ret |= of_property_read_u32(info_np, "fdd", &info->fdd);
>> +     ret |= of_property_read_u32(info_np, "sync-edge", &info->sync_edge);
>> +     ret |= of_property_read_u32(info_np, "sync-ctrl", &info->sync_ctrl);
>> +     ret |= of_property_read_u32(info_np, "raster-order", &info->raster_order);
>> +     ret |= of_property_read_u32(info_np, "fifo-th", &info->fifo_th);
>
> Shouldn't these values all be documented somewhere in the devictree docs?
> Or are they somewhat standardized?

Yeah, I guess I need to add DT docs..  I didn't realize this earlier.

BR,
-R

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 4/4] drm/tilcdc: add support for LCD panels (v4)
@ 2013-01-24 14:40       ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-24 14:40 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, Jan 24, 2013 at 7:08 AM, Daniel Vetter <daniel@ffwll.ch> wrote:
> On Tue, Jan 22, 2013 at 04:36:25PM -0600, Rob Clark wrote:
>> Add an output panel driver for LCD panels.  Tested with LCD3 cape on
>> beaglebone.
>>
>> v1: original
>> v2: s/of_find_node_by_name()/of_get_child_by_name()/ from Pantelis
>>     Antoniou
>> v3: add backlight support
>> v4: rebase to latest of video timing helpers
>>
>> Signed-off-by: Rob Clark <robdclark@gmail.com>
>
> So given that I'm utterly lacking clue about all things of (which seems to
> be where all the magic in this patch lies) I'm just gonna ask a few funny
> questions.
>
> [cut]
>
>> +static int panel_connector_get_modes(struct drm_connector *connector)
>> +{
>> +     struct drm_device *dev = connector->dev;
>> +     struct panel_connector *panel_connector = to_panel_connector(connector);
>> +     struct display_timings *timings = panel_connector->mod->timings;
>> +     int i;
>> +
>> +     for (i = 0; i < timings->num_timings; i++) {
>> +             struct drm_display_mode *mode = drm_mode_create(dev);
>> +             struct videomode vm;
>> +
>> +             if (videomode_from_timing(timings, &vm, i))
>> +                     break;
>> +
>> +             drm_display_mode_from_videomode(&vm, mode);
>
> Why do we need to jump through the intermediate videomode thing here? Is
> that a deficiency of the of/videomode stuff?

there is a helper that cut through the intermediate videomode
structure, although it assumed you are doing that at the point where
you still have the 'struct device_node *' (in the probe code) which
didn't really fit well for how I had structured things.  So I just
skipped it.  I guess I could have gone straight to array of
drm_display_mode in the probe call, and then copied them here in
get_modes() but this just seemed a bit easier.

> [cut]
>
>> +     ret |= of_property_read_u32(info_np, "ac-bias", &info->ac_bias);
>> +     ret |= of_property_read_u32(info_np, "ac-bias-intrpt", &info->ac_bias_intrpt);
>> +     ret |= of_property_read_u32(info_np, "dma-burst-sz", &info->dma_burst_sz);
>> +     ret |= of_property_read_u32(info_np, "bpp", &info->bpp);
>> +     ret |= of_property_read_u32(info_np, "fdd", &info->fdd);
>> +     ret |= of_property_read_u32(info_np, "sync-edge", &info->sync_edge);
>> +     ret |= of_property_read_u32(info_np, "sync-ctrl", &info->sync_ctrl);
>> +     ret |= of_property_read_u32(info_np, "raster-order", &info->raster_order);
>> +     ret |= of_property_read_u32(info_np, "fifo-th", &info->fifo_th);
>
> Shouldn't these values all be documented somewhere in the devictree docs?
> Or are they somewhat standardized?

Yeah, I guess I need to add DT docs..  I didn't realize this earlier.

BR,
-R

^ permalink raw reply	[flat|nested] 66+ messages in thread

* RE: [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
  2013-01-22 22:36   ` Rob Clark
  (?)
@ 2013-01-25 13:19     ` Mohammed, Afzal
  -1 siblings, 0 replies; 66+ messages in thread
From: Mohammed, Afzal @ 2013-01-25 13:19 UTC (permalink / raw)
  To: Rob Clark, dri-devel
  Cc: linux-omap, linux-arm-kernel, patches, linux-fbdev, Mike Turquette

SGkgUm9iLA0KDQpPbiBXZWQsIEphbiAyMywgMjAxMyBhdCAwNDowNjoyMiwgUm9iIENsYXJrIHdy
b3RlOg0KDQo+IEEgc2ltcGxlIERSTS9LTVMgZHJpdmVyIGZvciB0aGUgVEkgTENEIENvbnRyb2xs
ZXIgZm91bmQgaW4gdmFyaW91cw0KPiBzbWFsbGVyIFRJIHBhcnRzIChBTTMzeHgsIE9NQVBMMTM4
LCBldGMpLiAgVGhpcyBkcml2ZXIgdXNlcyB0aGUNCg0KPiArdm9pZCB0aWxjZGNfY3J0Y191cGRh
dGVfY2xrKHN0cnVjdCBkcm1fY3J0YyAqY3J0YykNCg0KPiArCS8qIGluIHJhc3RlciBtb2RlLCBt
aW5pbXVtIGRpdmlzb3IgaXMgMjogKi8NCj4gKwlyZXQgPSBjbGtfc2V0X3JhdGUocHJpdi0+ZGlz
cF9jbGssIGNydGMtPm1vZGUuY2xvY2sgKiAxMDAwICogMik7DQoNClRoZXNlIHRoaW5ncyBjYW4g
YmV0dGVyIGJlIGhhbmRsZWQgd2l0aCBkaXZpZGVyIGNsb2NrIGhhdmluZyBhDQptaW5pbXVtIHZh
bHVlIChiZWluZyBkaXNjdXNzZWQgd2l0aCBNaWtlIG9uIGhvdyBleGFjdGx5IGl0IHNob3VsZA0K
YmUpIGFuZCBpbnN0ZWFkIG9mIHNldHRpbmcgcmF0ZSBvdmVyIGEgcGxhdGZvcm0gc3BlY2lmaWMg
Y2xvY2ssDQp5b3UgY2FuIHNldCByYXRlIG92ZXIgbGNkIGNsb2NrIHVzaW5nIFNFVF9SQVRFX1BB
UkVOVCBhdCBwbGF0Zm9ybQ0KbGV2ZWwsIG1vcmUgYmVsb3csDQoNCj4gKwkvKiBDb25maWd1cmUg
dGhlIExDRCBjbG9jayBkaXZpc29yLiAqLw0KPiArCXRpbGNkY193cml0ZShkZXYsIExDRENfQ1RS
TF9SRUcsIExDRENfQ0xLX0RJVklTT1IoZGl2KSB8DQo+ICsJCQlMQ0RDX1JBU1RFUl9NT0RFKTsN
Cj4gKw0KPiArCWlmIChwcml2LT5yZXYgPT0gMikNCj4gKwkJdGlsY2RjX3NldChkZXYsIExDRENf
Q0xLX0VOQUJMRV9SRUcsDQo+ICsJCQkJTENEQ19WMl9ETUFfQ0xLX0VOIHwgTENEQ19WMl9MSURE
X0NMS19FTiB8DQo+ICsJCQkJTENEQ19WMl9DT1JFX0NMS19FTik7DQoNCk1pa2Ugd2FzIHN1Z2dl
c3RpbmcgdG8gbW9kZWwgdGhlIGFib3ZlIHVzaW5nIGdhdGUvZGl2aWRlci9jb21wb3NpdGUNCmNs
b2NrcyBvZiBDQ0YgaW4gdGhlIEZCIGRyaXZlci4NCg0KPiArCXByaXYtPmNsayA9IGNsa19nZXQo
ZGV2LT5kZXYsICJmY2siKTsNCj4gKwlpZiAoSVNfRVJSKHByaXYtPmNsaykpIHsNCj4gKwkJZGV2
X2VycihkZXYtPmRldiwgImZhaWxlZCB0byBnZXQgZnVuY3Rpb25hbCBjbG9ja1xuIik7DQo+ICsJ
CXJldCA9IC1FTk9ERVY7DQo+ICsJCWdvdG8gZmFpbDsNCj4gKwl9DQo+ICsNCj4gKwlwcml2LT5k
aXNwX2NsayA9IGNsa19nZXQoZGV2LT5kZXYsICJkcGxsX2Rpc3BfY2siKTsNCj4gKwlpZiAoSVNf
RVJSKHByaXYtPmNsaykpIHsNCj4gKwkJZGV2X2VycihkZXYtPmRldiwgImZhaWxlZCB0byBnZXQg
ZGlzcGxheSBjbG9ja1xuIik7DQo+ICsJCXJldCA9IC1FTk9ERVY7DQo+ICsJCWdvdG8gZmFpbDsN
Cj4gKwl9DQoNCiJkcGxsX2Rpc3BfY2siIGlzIGEgcGxhdGZvcm0gc3BlY2lmaWMgbWF0dGVyLCBk
cml2ZXIgc2hvdWxkIG5vdA0KYmUgaGFuZGxpbmcgcGxhdGZvcm0gc3BlY2lmaWNzLiBBbmQgdGhp
cyB3b24ndCB3b3JrIG9uIERhVmluY2ksDQp5b3UgY2FuIHByb2JhYmx5IG1ha2UgdXNlIG9mDQoN
Cm15IHNlcmllcyAiW1BBVENIIHYyIDAvNF0gQVJNOiBBTTMzNXg6IExDREMgcGxhdGZvcm0gc3Vw
cG9ydCINCg0Kb3Igc29tZXRoaW5nIHNpbWlsYXIgdG8gb3ZlcmNvbWUgdGhpcy4NCg0KUmVnYXJk
cw0KQWZ6YWwNCg0KDQo

^ permalink raw reply	[flat|nested] 66+ messages in thread

* RE: [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-25 13:19     ` Mohammed, Afzal
  0 siblings, 0 replies; 66+ messages in thread
From: Mohammed, Afzal @ 2013-01-25 13:19 UTC (permalink / raw)
  To: Rob Clark, dri-devel
  Cc: linux-omap, linux-arm-kernel, patches, linux-fbdev, Mike Turquette

Hi Rob,

On Wed, Jan 23, 2013 at 04:06:22, 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

> +void tilcdc_crtc_update_clk(struct drm_crtc *crtc)

> +	/* in raster mode, minimum divisor is 2: */
> +	ret = clk_set_rate(priv->disp_clk, crtc->mode.clock * 1000 * 2);

These things can better be handled with divider clock having a
minimum value (being discussed with Mike on how exactly it should
be) and instead of setting rate over a platform specific clock,
you can set rate over lcd clock using SET_RATE_PARENT at platform
level, more below,

> +	/* Configure the LCD clock divisor. */
> +	tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) |
> +			LCDC_RASTER_MODE);
> +
> +	if (priv->rev == 2)
> +		tilcdc_set(dev, LCDC_CLK_ENABLE_REG,
> +				LCDC_V2_DMA_CLK_EN | LCDC_V2_LIDD_CLK_EN |
> +				LCDC_V2_CORE_CLK_EN);

Mike was suggesting to model the above using gate/divider/composite
clocks of CCF in the FB driver.

> +	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;
> +	}

"dpll_disp_ck" is a platform specific matter, driver should not
be handling platform specifics. And this won't work on DaVinci,
you can probably make use of

my series "[PATCH v2 0/4] ARM: AM335x: LCDC platform support"

or something similar to overcome this.

Regards
Afzal



^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-25 13:19     ` Mohammed, Afzal
  0 siblings, 0 replies; 66+ messages in thread
From: Mohammed, Afzal @ 2013-01-25 13:19 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Rob,

On Wed, Jan 23, 2013 at 04:06:22, 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

> +void tilcdc_crtc_update_clk(struct drm_crtc *crtc)

> +	/* in raster mode, minimum divisor is 2: */
> +	ret = clk_set_rate(priv->disp_clk, crtc->mode.clock * 1000 * 2);

These things can better be handled with divider clock having a
minimum value (being discussed with Mike on how exactly it should
be) and instead of setting rate over a platform specific clock,
you can set rate over lcd clock using SET_RATE_PARENT at platform
level, more below,

> +	/* Configure the LCD clock divisor. */
> +	tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) |
> +			LCDC_RASTER_MODE);
> +
> +	if (priv->rev == 2)
> +		tilcdc_set(dev, LCDC_CLK_ENABLE_REG,
> +				LCDC_V2_DMA_CLK_EN | LCDC_V2_LIDD_CLK_EN |
> +				LCDC_V2_CORE_CLK_EN);

Mike was suggesting to model the above using gate/divider/composite
clocks of CCF in the FB driver.

> +	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;
> +	}

"dpll_disp_ck" is a platform specific matter, driver should not
be handling platform specifics. And this won't work on DaVinci,
you can probably make use of

my series "[PATCH v2 0/4] ARM: AM335x: LCDC platform support"

or something similar to overcome this.

Regards
Afzal

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
  2013-01-25 13:19     ` Mohammed, Afzal
  (?)
@ 2013-01-25 13:59       ` Rob Clark
  -1 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-25 13:59 UTC (permalink / raw)
  To: Mohammed, Afzal
  Cc: dri-devel, linux-omap, linux-arm-kernel, patches, linux-fbdev,
	Mike Turquette

On Fri, Jan 25, 2013 at 7:19 AM, Mohammed, Afzal <afzal@ti.com> wrote:
> Hi Rob,
>
> On Wed, Jan 23, 2013 at 04:06:22, 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
>
>> +void tilcdc_crtc_update_clk(struct drm_crtc *crtc)
>
>> +     /* in raster mode, minimum divisor is 2: */
>> +     ret = clk_set_rate(priv->disp_clk, crtc->mode.clock * 1000 * 2);
>
> These things can better be handled with divider clock having a
> minimum value (being discussed with Mike on how exactly it should
> be) and instead of setting rate over a platform specific clock,
> you can set rate over lcd clock using SET_RATE_PARENT at platform
> level, more below,

I looked at that patch you were proposing for da8xx-fb..  to be
honest, it did not seem simpler to me, so I was sort of failing to see
the benefit..

>> +     /* Configure the LCD clock divisor. */
>> +     tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) |
>> +                     LCDC_RASTER_MODE);
>> +
>> +     if (priv->rev = 2)
>> +             tilcdc_set(dev, LCDC_CLK_ENABLE_REG,
>> +                             LCDC_V2_DMA_CLK_EN | LCDC_V2_LIDD_CLK_EN |
>> +                             LCDC_V2_CORE_CLK_EN);
>
> Mike was suggesting to model the above using gate/divider/composite
> clocks of CCF in the FB driver.
>
>> +     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;
>> +     }
>
> "dpll_disp_ck" is a platform specific matter, driver should not
> be handling platform specifics. And this won't work on DaVinci,
> you can probably make use of

meaning the clock has a different name on davinci, or?  Presumably
there must be some clock generating the pixel clock for the display,
but I confess to not being too familiar with the details on davinci..

BR,
-R

> my series "[PATCH v2 0/4] ARM: AM335x: LCDC platform support"
>
> or something similar to overcome this.
>
> Regards
> Afzal
>
>

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-25 13:59       ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-25 13:59 UTC (permalink / raw)
  To: Mohammed, Afzal
  Cc: dri-devel, linux-omap, linux-arm-kernel, patches, linux-fbdev,
	Mike Turquette

On Fri, Jan 25, 2013 at 7:19 AM, Mohammed, Afzal <afzal@ti.com> wrote:
> Hi Rob,
>
> On Wed, Jan 23, 2013 at 04:06:22, 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
>
>> +void tilcdc_crtc_update_clk(struct drm_crtc *crtc)
>
>> +     /* in raster mode, minimum divisor is 2: */
>> +     ret = clk_set_rate(priv->disp_clk, crtc->mode.clock * 1000 * 2);
>
> These things can better be handled with divider clock having a
> minimum value (being discussed with Mike on how exactly it should
> be) and instead of setting rate over a platform specific clock,
> you can set rate over lcd clock using SET_RATE_PARENT at platform
> level, more below,

I looked at that patch you were proposing for da8xx-fb..  to be
honest, it did not seem simpler to me, so I was sort of failing to see
the benefit..

>> +     /* Configure the LCD clock divisor. */
>> +     tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) |
>> +                     LCDC_RASTER_MODE);
>> +
>> +     if (priv->rev == 2)
>> +             tilcdc_set(dev, LCDC_CLK_ENABLE_REG,
>> +                             LCDC_V2_DMA_CLK_EN | LCDC_V2_LIDD_CLK_EN |
>> +                             LCDC_V2_CORE_CLK_EN);
>
> Mike was suggesting to model the above using gate/divider/composite
> clocks of CCF in the FB driver.
>
>> +     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;
>> +     }
>
> "dpll_disp_ck" is a platform specific matter, driver should not
> be handling platform specifics. And this won't work on DaVinci,
> you can probably make use of

meaning the clock has a different name on davinci, or?  Presumably
there must be some clock generating the pixel clock for the display,
but I confess to not being too familiar with the details on davinci..

BR,
-R

> my series "[PATCH v2 0/4] ARM: AM335x: LCDC platform support"
>
> or something similar to overcome this.
>
> Regards
> Afzal
>
>

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-25 13:59       ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-25 13:59 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, Jan 25, 2013 at 7:19 AM, Mohammed, Afzal <afzal@ti.com> wrote:
> Hi Rob,
>
> On Wed, Jan 23, 2013 at 04:06:22, 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
>
>> +void tilcdc_crtc_update_clk(struct drm_crtc *crtc)
>
>> +     /* in raster mode, minimum divisor is 2: */
>> +     ret = clk_set_rate(priv->disp_clk, crtc->mode.clock * 1000 * 2);
>
> These things can better be handled with divider clock having a
> minimum value (being discussed with Mike on how exactly it should
> be) and instead of setting rate over a platform specific clock,
> you can set rate over lcd clock using SET_RATE_PARENT at platform
> level, more below,

I looked at that patch you were proposing for da8xx-fb..  to be
honest, it did not seem simpler to me, so I was sort of failing to see
the benefit..

>> +     /* Configure the LCD clock divisor. */
>> +     tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) |
>> +                     LCDC_RASTER_MODE);
>> +
>> +     if (priv->rev == 2)
>> +             tilcdc_set(dev, LCDC_CLK_ENABLE_REG,
>> +                             LCDC_V2_DMA_CLK_EN | LCDC_V2_LIDD_CLK_EN |
>> +                             LCDC_V2_CORE_CLK_EN);
>
> Mike was suggesting to model the above using gate/divider/composite
> clocks of CCF in the FB driver.
>
>> +     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;
>> +     }
>
> "dpll_disp_ck" is a platform specific matter, driver should not
> be handling platform specifics. And this won't work on DaVinci,
> you can probably make use of

meaning the clock has a different name on davinci, or?  Presumably
there must be some clock generating the pixel clock for the display,
but I confess to not being too familiar with the details on davinci..

BR,
-R

> my series "[PATCH v2 0/4] ARM: AM335x: LCDC platform support"
>
> or something similar to overcome this.
>
> Regards
> Afzal
>
>

^ permalink raw reply	[flat|nested] 66+ messages in thread

* RE: [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
  2013-01-25 13:59       ` Rob Clark
  (?)
@ 2013-01-25 14:15         ` Mohammed, Afzal
  -1 siblings, 0 replies; 66+ messages in thread
From: Mohammed, Afzal @ 2013-01-25 14:15 UTC (permalink / raw)
  To: Rob Clark
  Cc: dri-devel, linux-omap, linux-arm-kernel, patches, linux-fbdev,
	Mike Turquette, Nori, Sekhar

SGkgUm9iLA0KDQpPbiBGcmksIEphbiAyNSwgMjAxMyBhdCAxOToyOTo0MCwgUm9iIENsYXJrIHdy
b3RlOg0KPiBPbiBGcmksIEphbiAyNSwgMjAxMyBhdCA3OjE5IEFNLCBNb2hhbW1lZCwgQWZ6YWwg
PGFmemFsQHRpLmNvbT4gd3JvdGU6DQo+ID4gT24gV2VkLCBKYW4gMjMsIDIwMTMgYXQgMDQ6MDY6
MjIsIFJvYiBDbGFyayB3cm90ZToNCg0KPiA+PiBBIHNpbXBsZSBEUk0vS01TIGRyaXZlciBmb3Ig
dGhlIFRJIExDRCBDb250cm9sbGVyIGZvdW5kIGluIHZhcmlvdXMNCj4gPj4gc21hbGxlciBUSSBw
YXJ0cyAoQU0zM3h4LCBPTUFQTDEzOCwgZXRjKS4gIFRoaXMgZHJpdmVyIHVzZXMgdGhlDQoNCj4g
Pj4gK3ZvaWQgdGlsY2RjX2NydGNfdXBkYXRlX2NsayhzdHJ1Y3QgZHJtX2NydGMgKmNydGMpDQo+
ID4NCj4gPj4gKyAgICAgLyogaW4gcmFzdGVyIG1vZGUsIG1pbmltdW0gZGl2aXNvciBpcyAyOiAq
Lw0KPiA+PiArICAgICByZXQgPSBjbGtfc2V0X3JhdGUocHJpdi0+ZGlzcF9jbGssIGNydGMtPm1v
ZGUuY2xvY2sgKiAxMDAwICogMik7DQoNCj4gPiBUaGVzZSB0aGluZ3MgY2FuIGJldHRlciBiZSBo
YW5kbGVkIHdpdGggZGl2aWRlciBjbG9jayBoYXZpbmcgYQ0KPiA+IG1pbmltdW0gdmFsdWUgKGJl
aW5nIGRpc2N1c3NlZCB3aXRoIE1pa2Ugb24gaG93IGV4YWN0bHkgaXQgc2hvdWxkDQo+ID4gYmUp
IGFuZCBpbnN0ZWFkIG9mIHNldHRpbmcgcmF0ZSBvdmVyIGEgcGxhdGZvcm0gc3BlY2lmaWMgY2xv
Y2ssDQo+ID4geW91IGNhbiBzZXQgcmF0ZSBvdmVyIGxjZCBjbG9jayB1c2luZyBTRVRfUkFURV9Q
QVJFTlQgYXQgcGxhdGZvcm0NCj4gPiBsZXZlbCwgbW9yZSBiZWxvdywNCg0KPiBJIGxvb2tlZCBh
dCB0aGF0IHBhdGNoIHlvdSB3ZXJlIHByb3Bvc2luZyBmb3IgZGE4eHgtZmIuLiAgdG8gYmUNCj4g
aG9uZXN0LCBpdCBkaWQgbm90IHNlZW0gc2ltcGxlciB0byBtZSwgc28gSSB3YXMgc29ydCBvZiBm
YWlsaW5nIHRvIHNlZQ0KPiB0aGUgYmVuZWZpdC4uDQoNCkl0J3Mgbm90IGFib3V0IGJlaW5nIHNp
bXBsZSwgYnV0IG5vdCBkb2luZyB0aGUgd3Jvbmcgd2F5LCBoZXJlIHlvdSBhcmUNCnJlbHlpbmcg
b24gYSBwbGF0Zm9ybSBzcGVjaWZpYyBjbG9jayBpbiBhIGRyaXZlciwgdGhpbmsgYWJvdXQgdGhl
IGNhc2UNCndoZXJlIHNhbWUgSVAgaXMgdXNlZCBvbiBhbm90aGVyIHBsYXRmb3JtLiBFaXRoZXIg
d2F5IGl0IGlzIG5vdCBhIGdvb2QNCnRoaW5nIHRvIGhhbmRsZSBwbGF0Zm9ybSBzcGVjaWZpYyBk
ZXRhaWxzIChhYm91dCBkaXNwX2NsaykgaW4gZHJpdmVyLg0KDQpBbmQgTWlrZSBtZW50aW9uZWQg
dGhhdCBvbmUgb2YgZGVzaWduIGdvYWxzIG9mIENDRiBpcyB0byBtb2RlbCB0aGVzZQ0Ka2luZHMg
b2YgY2xvY2tzIGluIElQJ3MuDQoNCj4gPj4gKyAgICAgLyogQ29uZmlndXJlIHRoZSBMQ0QgY2xv
Y2sgZGl2aXNvci4gKi8NCj4gPj4gKyAgICAgdGlsY2RjX3dyaXRlKGRldiwgTENEQ19DVFJMX1JF
RywgTENEQ19DTEtfRElWSVNPUihkaXYpIHwNCj4gPj4gKyAgICAgICAgICAgICAgICAgICAgIExD
RENfUkFTVEVSX01PREUpOw0KPiA+PiArDQo+ID4+ICsgICAgIGlmIChwcml2LT5yZXYgPT0gMikN
Cj4gPj4gKyAgICAgICAgICAgICB0aWxjZGNfc2V0KGRldiwgTENEQ19DTEtfRU5BQkxFX1JFRywN
Cj4gPj4gKyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTENEQ19WMl9ETUFfQ0xLX0VOIHwg
TENEQ19WMl9MSUREX0NMS19FTiB8DQo+ID4+ICsgICAgICAgICAgICAgICAgICAgICAgICAgICAg
IExDRENfVjJfQ09SRV9DTEtfRU4pOw0KPiA+DQo+ID4gTWlrZSB3YXMgc3VnZ2VzdGluZyB0byBt
b2RlbCB0aGUgYWJvdmUgdXNpbmcgZ2F0ZS9kaXZpZGVyL2NvbXBvc2l0ZQ0KPiA+IGNsb2NrcyBv
ZiBDQ0YgaW4gdGhlIEZCIGRyaXZlci4NCg0KPiA+PiArICAgICBwcml2LT5jbGsgPSBjbGtfZ2V0
KGRldi0+ZGV2LCAiZmNrIik7DQo+ID4+ICsgICAgIGlmIChJU19FUlIocHJpdi0+Y2xrKSkgew0K
PiA+PiArICAgICAgICAgICAgIGRldl9lcnIoZGV2LT5kZXYsICJmYWlsZWQgdG8gZ2V0IGZ1bmN0
aW9uYWwgY2xvY2tcbiIpOw0KPiA+PiArICAgICAgICAgICAgIHJldCA9IC1FTk9ERVY7DQo+ID4+
ICsgICAgICAgICAgICAgZ290byBmYWlsOw0KPiA+PiArICAgICB9DQo+ID4+ICsNCj4gPj4gKyAg
ICAgcHJpdi0+ZGlzcF9jbGsgPSBjbGtfZ2V0KGRldi0+ZGV2LCAiZHBsbF9kaXNwX2NrIik7DQo+
ID4+ICsgICAgIGlmIChJU19FUlIocHJpdi0+Y2xrKSkgew0KPiA+PiArICAgICAgICAgICAgIGRl
dl9lcnIoZGV2LT5kZXYsICJmYWlsZWQgdG8gZ2V0IGRpc3BsYXkgY2xvY2tcbiIpOw0KPiA+PiAr
ICAgICAgICAgICAgIHJldCA9IC1FTk9ERVY7DQo+ID4+ICsgICAgICAgICAgICAgZ290byBmYWls
Ow0KPiA+PiArICAgICB9DQo+ID4NCj4gPiAiZHBsbF9kaXNwX2NrIiBpcyBhIHBsYXRmb3JtIHNw
ZWNpZmljIG1hdHRlciwgZHJpdmVyIHNob3VsZCBub3QNCj4gPiBiZSBoYW5kbGluZyBwbGF0Zm9y
bSBzcGVjaWZpY3MuIEFuZCB0aGlzIHdvbid0IHdvcmsgb24gRGFWaW5jaSwNCj4gPiB5b3UgY2Fu
IHByb2JhYmx5IG1ha2UgdXNlIG9mDQo+IA0KPiBtZWFuaW5nIHRoZSBjbG9jayBoYXMgYSBkaWZm
ZXJlbnQgbmFtZSBvbiBkYXZpbmNpLCBvcj8gIFByZXN1bWFibHkNCj4gdGhlcmUgbXVzdCBiZSBz
b21lIGNsb2NrIGdlbmVyYXRpbmcgdGhlIHBpeGVsIGNsb2NrIGZvciB0aGUgZGlzcGxheSwNCj4g
YnV0IEkgY29uZmVzcyB0byBub3QgYmVpbmcgdG9vIGZhbWlsaWFyIHdpdGggdGhlIGRldGFpbHMg
b24gZGF2aW5jaS4uDQoNClRoZSBvbmx5IG9wdGlvbiBmb3IgdGhlIGRyaXZlciBpbiBEYVZpbmNp
IGlzIHRvIGNvbmZpZ3VyZSBjbG9jayByYXRlDQp1c2luZyB0aGUgZGl2aWRlciBvZiBMQ0RDIElQ
LCBpdCBoYXMgImZjayIsIHdoaWNoIGdpdmVzIGEgcmF0ZQ0KZGVjaWRlZCBieSBpdHMgYW5jZXN0
b3JzLg0KDQpSZWdhcmRzDQpBZnphbA0K

^ permalink raw reply	[flat|nested] 66+ messages in thread

* RE: [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-25 14:15         ` Mohammed, Afzal
  0 siblings, 0 replies; 66+ messages in thread
From: Mohammed, Afzal @ 2013-01-25 14:15 UTC (permalink / raw)
  To: Rob Clark
  Cc: dri-devel, linux-omap, linux-arm-kernel, patches, linux-fbdev,
	Mike Turquette, Nori, Sekhar

Hi Rob,

On Fri, Jan 25, 2013 at 19:29:40, Rob Clark wrote:
> On Fri, Jan 25, 2013 at 7:19 AM, Mohammed, Afzal <afzal@ti.com> wrote:
> > On Wed, Jan 23, 2013 at 04:06:22, 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

> >> +void tilcdc_crtc_update_clk(struct drm_crtc *crtc)
> >
> >> +     /* in raster mode, minimum divisor is 2: */
> >> +     ret = clk_set_rate(priv->disp_clk, crtc->mode.clock * 1000 * 2);

> > These things can better be handled with divider clock having a
> > minimum value (being discussed with Mike on how exactly it should
> > be) and instead of setting rate over a platform specific clock,
> > you can set rate over lcd clock using SET_RATE_PARENT at platform
> > level, more below,

> I looked at that patch you were proposing for da8xx-fb..  to be
> honest, it did not seem simpler to me, so I was sort of failing to see
> the benefit..

It's not about being simple, but not doing the wrong way, here you are
relying on a platform specific clock in a driver, think about the case
where same IP is used on another platform. Either way it is not a good
thing to handle platform specific details (about disp_clk) in driver.

And Mike mentioned that one of design goals of CCF is to model these
kinds of clocks in IP's.

> >> +     /* Configure the LCD clock divisor. */
> >> +     tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) |
> >> +                     LCDC_RASTER_MODE);
> >> +
> >> +     if (priv->rev == 2)
> >> +             tilcdc_set(dev, LCDC_CLK_ENABLE_REG,
> >> +                             LCDC_V2_DMA_CLK_EN | LCDC_V2_LIDD_CLK_EN |
> >> +                             LCDC_V2_CORE_CLK_EN);
> >
> > Mike was suggesting to model the above using gate/divider/composite
> > clocks of CCF in the FB driver.

> >> +     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;
> >> +     }
> >
> > "dpll_disp_ck" is a platform specific matter, driver should not
> > be handling platform specifics. And this won't work on DaVinci,
> > you can probably make use of
> 
> meaning the clock has a different name on davinci, or?  Presumably
> there must be some clock generating the pixel clock for the display,
> but I confess to not being too familiar with the details on davinci..

The only option for the driver in DaVinci is to configure clock rate
using the divider of LCDC IP, it has "fck", which gives a rate
decided by its ancestors.

Regards
Afzal

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-25 14:15         ` Mohammed, Afzal
  0 siblings, 0 replies; 66+ messages in thread
From: Mohammed, Afzal @ 2013-01-25 14:15 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Rob,

On Fri, Jan 25, 2013 at 19:29:40, Rob Clark wrote:
> On Fri, Jan 25, 2013 at 7:19 AM, Mohammed, Afzal <afzal@ti.com> wrote:
> > On Wed, Jan 23, 2013 at 04:06:22, 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

> >> +void tilcdc_crtc_update_clk(struct drm_crtc *crtc)
> >
> >> +     /* in raster mode, minimum divisor is 2: */
> >> +     ret = clk_set_rate(priv->disp_clk, crtc->mode.clock * 1000 * 2);

> > These things can better be handled with divider clock having a
> > minimum value (being discussed with Mike on how exactly it should
> > be) and instead of setting rate over a platform specific clock,
> > you can set rate over lcd clock using SET_RATE_PARENT at platform
> > level, more below,

> I looked at that patch you were proposing for da8xx-fb..  to be
> honest, it did not seem simpler to me, so I was sort of failing to see
> the benefit..

It's not about being simple, but not doing the wrong way, here you are
relying on a platform specific clock in a driver, think about the case
where same IP is used on another platform. Either way it is not a good
thing to handle platform specific details (about disp_clk) in driver.

And Mike mentioned that one of design goals of CCF is to model these
kinds of clocks in IP's.

> >> +     /* Configure the LCD clock divisor. */
> >> +     tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) |
> >> +                     LCDC_RASTER_MODE);
> >> +
> >> +     if (priv->rev == 2)
> >> +             tilcdc_set(dev, LCDC_CLK_ENABLE_REG,
> >> +                             LCDC_V2_DMA_CLK_EN | LCDC_V2_LIDD_CLK_EN |
> >> +                             LCDC_V2_CORE_CLK_EN);
> >
> > Mike was suggesting to model the above using gate/divider/composite
> > clocks of CCF in the FB driver.

> >> +     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;
> >> +     }
> >
> > "dpll_disp_ck" is a platform specific matter, driver should not
> > be handling platform specifics. And this won't work on DaVinci,
> > you can probably make use of
> 
> meaning the clock has a different name on davinci, or?  Presumably
> there must be some clock generating the pixel clock for the display,
> but I confess to not being too familiar with the details on davinci..

The only option for the driver in DaVinci is to configure clock rate
using the divider of LCDC IP, it has "fck", which gives a rate
decided by its ancestors.

Regards
Afzal

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
  2013-01-25 14:15         ` Mohammed, Afzal
  (?)
@ 2013-01-25 14:52           ` Rob Clark
  -1 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-25 14:52 UTC (permalink / raw)
  To: Mohammed, Afzal
  Cc: dri-devel, linux-omap, linux-arm-kernel, patches, linux-fbdev,
	Mike Turquette, Nori, Sekhar

On Fri, Jan 25, 2013 at 8:15 AM, Mohammed, Afzal <afzal@ti.com> wrote:
> Hi Rob,
>
> On Fri, Jan 25, 2013 at 19:29:40, Rob Clark wrote:
>> On Fri, Jan 25, 2013 at 7:19 AM, Mohammed, Afzal <afzal@ti.com> wrote:
>> > On Wed, Jan 23, 2013 at 04:06:22, 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
>
>> >> +void tilcdc_crtc_update_clk(struct drm_crtc *crtc)
>> >
>> >> +     /* in raster mode, minimum divisor is 2: */
>> >> +     ret = clk_set_rate(priv->disp_clk, crtc->mode.clock * 1000 * 2);
>
>> > These things can better be handled with divider clock having a
>> > minimum value (being discussed with Mike on how exactly it should
>> > be) and instead of setting rate over a platform specific clock,
>> > you can set rate over lcd clock using SET_RATE_PARENT at platform
>> > level, more below,
>
>> I looked at that patch you were proposing for da8xx-fb..  to be
>> honest, it did not seem simpler to me, so I was sort of failing to see
>> the benefit..
>
> It's not about being simple, but not doing the wrong way, here you are
> relying on a platform specific clock in a driver, think about the case
> where same IP is used on another platform. Either way it is not a good
> thing to handle platform specific details (about disp_clk) in driver.

Right, but I was trying to understand what was the benefit that the
added complexity is.  I didn't realize on davinci that you are limited
to just setting divider values (which is going to drastically limit
the timings you can generate, although maybe not an issue if all you
support is a fixed lcd panel).

> And Mike mentioned that one of design goals of CCF is to model these
> kinds of clocks in IP's.
>
>> >> +     /* Configure the LCD clock divisor. */
>> >> +     tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) |
>> >> +                     LCDC_RASTER_MODE);
>> >> +
>> >> +     if (priv->rev = 2)
>> >> +             tilcdc_set(dev, LCDC_CLK_ENABLE_REG,
>> >> +                             LCDC_V2_DMA_CLK_EN | LCDC_V2_LIDD_CLK_EN |
>> >> +                             LCDC_V2_CORE_CLK_EN);
>> >
>> > Mike was suggesting to model the above using gate/divider/composite
>> > clocks of CCF in the FB driver.
>
>> >> +     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;
>> >> +     }
>> >
>> > "dpll_disp_ck" is a platform specific matter, driver should not
>> > be handling platform specifics. And this won't work on DaVinci,
>> > you can probably make use of
>>
>> meaning the clock has a different name on davinci, or?  Presumably
>> there must be some clock generating the pixel clock for the display,
>> but I confess to not being too familiar with the details on davinci..
>
> The only option for the driver in DaVinci is to configure clock rate
> using the divider of LCDC IP, it has "fck", which gives a rate
> decided by its ancestors.

Well, from looking at the other patch series it seems CCF doesn't
support minimum divider yet.  And davinci doesn't support CCF yet.  So
at the moment all this discussion is a bit moot.  I'd propose leaving
the driver as it is for now, because that at least makes it useful on
am33xx.  And when CCF and davinci have the needed support in place,
then send a patch to change the clock handling in tilcdc.  I don't
actually have any davinci hw to test on, but I can easily test on
am33xx.

BR,
-R

> Regards
> Afzal

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-25 14:52           ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-25 14:52 UTC (permalink / raw)
  To: Mohammed, Afzal
  Cc: dri-devel, linux-omap, linux-arm-kernel, patches, linux-fbdev,
	Mike Turquette, Nori, Sekhar

On Fri, Jan 25, 2013 at 8:15 AM, Mohammed, Afzal <afzal@ti.com> wrote:
> Hi Rob,
>
> On Fri, Jan 25, 2013 at 19:29:40, Rob Clark wrote:
>> On Fri, Jan 25, 2013 at 7:19 AM, Mohammed, Afzal <afzal@ti.com> wrote:
>> > On Wed, Jan 23, 2013 at 04:06:22, 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
>
>> >> +void tilcdc_crtc_update_clk(struct drm_crtc *crtc)
>> >
>> >> +     /* in raster mode, minimum divisor is 2: */
>> >> +     ret = clk_set_rate(priv->disp_clk, crtc->mode.clock * 1000 * 2);
>
>> > These things can better be handled with divider clock having a
>> > minimum value (being discussed with Mike on how exactly it should
>> > be) and instead of setting rate over a platform specific clock,
>> > you can set rate over lcd clock using SET_RATE_PARENT at platform
>> > level, more below,
>
>> I looked at that patch you were proposing for da8xx-fb..  to be
>> honest, it did not seem simpler to me, so I was sort of failing to see
>> the benefit..
>
> It's not about being simple, but not doing the wrong way, here you are
> relying on a platform specific clock in a driver, think about the case
> where same IP is used on another platform. Either way it is not a good
> thing to handle platform specific details (about disp_clk) in driver.

Right, but I was trying to understand what was the benefit that the
added complexity is.  I didn't realize on davinci that you are limited
to just setting divider values (which is going to drastically limit
the timings you can generate, although maybe not an issue if all you
support is a fixed lcd panel).

> And Mike mentioned that one of design goals of CCF is to model these
> kinds of clocks in IP's.
>
>> >> +     /* Configure the LCD clock divisor. */
>> >> +     tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) |
>> >> +                     LCDC_RASTER_MODE);
>> >> +
>> >> +     if (priv->rev == 2)
>> >> +             tilcdc_set(dev, LCDC_CLK_ENABLE_REG,
>> >> +                             LCDC_V2_DMA_CLK_EN | LCDC_V2_LIDD_CLK_EN |
>> >> +                             LCDC_V2_CORE_CLK_EN);
>> >
>> > Mike was suggesting to model the above using gate/divider/composite
>> > clocks of CCF in the FB driver.
>
>> >> +     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;
>> >> +     }
>> >
>> > "dpll_disp_ck" is a platform specific matter, driver should not
>> > be handling platform specifics. And this won't work on DaVinci,
>> > you can probably make use of
>>
>> meaning the clock has a different name on davinci, or?  Presumably
>> there must be some clock generating the pixel clock for the display,
>> but I confess to not being too familiar with the details on davinci..
>
> The only option for the driver in DaVinci is to configure clock rate
> using the divider of LCDC IP, it has "fck", which gives a rate
> decided by its ancestors.

Well, from looking at the other patch series it seems CCF doesn't
support minimum divider yet.  And davinci doesn't support CCF yet.  So
at the moment all this discussion is a bit moot.  I'd propose leaving
the driver as it is for now, because that at least makes it useful on
am33xx.  And when CCF and davinci have the needed support in place,
then send a patch to change the clock handling in tilcdc.  I don't
actually have any davinci hw to test on, but I can easily test on
am33xx.

BR,
-R

> Regards
> Afzal

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-25 14:52           ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-25 14:52 UTC (permalink / raw)
  To: linux-arm-kernel

On Fri, Jan 25, 2013 at 8:15 AM, Mohammed, Afzal <afzal@ti.com> wrote:
> Hi Rob,
>
> On Fri, Jan 25, 2013 at 19:29:40, Rob Clark wrote:
>> On Fri, Jan 25, 2013 at 7:19 AM, Mohammed, Afzal <afzal@ti.com> wrote:
>> > On Wed, Jan 23, 2013 at 04:06:22, 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
>
>> >> +void tilcdc_crtc_update_clk(struct drm_crtc *crtc)
>> >
>> >> +     /* in raster mode, minimum divisor is 2: */
>> >> +     ret = clk_set_rate(priv->disp_clk, crtc->mode.clock * 1000 * 2);
>
>> > These things can better be handled with divider clock having a
>> > minimum value (being discussed with Mike on how exactly it should
>> > be) and instead of setting rate over a platform specific clock,
>> > you can set rate over lcd clock using SET_RATE_PARENT at platform
>> > level, more below,
>
>> I looked at that patch you were proposing for da8xx-fb..  to be
>> honest, it did not seem simpler to me, so I was sort of failing to see
>> the benefit..
>
> It's not about being simple, but not doing the wrong way, here you are
> relying on a platform specific clock in a driver, think about the case
> where same IP is used on another platform. Either way it is not a good
> thing to handle platform specific details (about disp_clk) in driver.

Right, but I was trying to understand what was the benefit that the
added complexity is.  I didn't realize on davinci that you are limited
to just setting divider values (which is going to drastically limit
the timings you can generate, although maybe not an issue if all you
support is a fixed lcd panel).

> And Mike mentioned that one of design goals of CCF is to model these
> kinds of clocks in IP's.
>
>> >> +     /* Configure the LCD clock divisor. */
>> >> +     tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) |
>> >> +                     LCDC_RASTER_MODE);
>> >> +
>> >> +     if (priv->rev == 2)
>> >> +             tilcdc_set(dev, LCDC_CLK_ENABLE_REG,
>> >> +                             LCDC_V2_DMA_CLK_EN | LCDC_V2_LIDD_CLK_EN |
>> >> +                             LCDC_V2_CORE_CLK_EN);
>> >
>> > Mike was suggesting to model the above using gate/divider/composite
>> > clocks of CCF in the FB driver.
>
>> >> +     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;
>> >> +     }
>> >
>> > "dpll_disp_ck" is a platform specific matter, driver should not
>> > be handling platform specifics. And this won't work on DaVinci,
>> > you can probably make use of
>>
>> meaning the clock has a different name on davinci, or?  Presumably
>> there must be some clock generating the pixel clock for the display,
>> but I confess to not being too familiar with the details on davinci..
>
> The only option for the driver in DaVinci is to configure clock rate
> using the divider of LCDC IP, it has "fck", which gives a rate
> decided by its ancestors.

Well, from looking at the other patch series it seems CCF doesn't
support minimum divider yet.  And davinci doesn't support CCF yet.  So
at the moment all this discussion is a bit moot.  I'd propose leaving
the driver as it is for now, because that at least makes it useful on
am33xx.  And when CCF and davinci have the needed support in place,
then send a patch to change the clock handling in tilcdc.  I don't
actually have any davinci hw to test on, but I can easily test on
am33xx.

BR,
-R

> Regards
> Afzal

^ permalink raw reply	[flat|nested] 66+ messages in thread

* RE: [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
  2013-01-25 14:52           ` Rob Clark
  (?)
@ 2013-01-28  9:56             ` Mohammed, Afzal
  -1 siblings, 0 replies; 66+ messages in thread
From: Mohammed, Afzal @ 2013-01-28  9:56 UTC (permalink / raw)
  To: Rob Clark
  Cc: dri-devel, linux-omap, linux-arm-kernel, patches, linux-fbdev,
	Mike Turquette, Nori, Sekhar

SGkgUm9iLA0KDQpPbiBGcmksIEphbiAyNSwgMjAxMyBhdCAyMDoyMjo1NSwgUm9iIENsYXJrIHdy
b3RlOg0KPiBPbiBGcmksIEphbiAyNSwgMjAxMyBhdCA4OjE1IEFNLCBNb2hhbW1lZCwgQWZ6YWwg
PGFmemFsQHRpLmNvbT4gd3JvdGU6DQoNCj4gPiBJdCdzIG5vdCBhYm91dCBiZWluZyBzaW1wbGUs
IGJ1dCBub3QgZG9pbmcgdGhlIHdyb25nIHdheSwgaGVyZSB5b3UgYXJlDQo+ID4gcmVseWluZyBv
biBhIHBsYXRmb3JtIHNwZWNpZmljIGNsb2NrIGluIGEgZHJpdmVyLCB0aGluayBhYm91dCB0aGUg
Y2FzZQ0KPiA+IHdoZXJlIHNhbWUgSVAgaXMgdXNlZCBvbiBhbm90aGVyIHBsYXRmb3JtLiBFaXRo
ZXIgd2F5IGl0IGlzIG5vdCBhIGdvb2QNCj4gPiB0aGluZyB0byBoYW5kbGUgcGxhdGZvcm0gc3Bl
Y2lmaWMgZGV0YWlscyAoYWJvdXQgZGlzcF9jbGspIGluIGRyaXZlci4NCg0KPiBSaWdodCwgYnV0
IEkgd2FzIHRyeWluZyB0byB1bmRlcnN0YW5kIHdoYXQgd2FzIHRoZSBiZW5lZml0IHRoYXQgdGhl
DQo+IGFkZGVkIGNvbXBsZXhpdHkgaXMuICBJIGRpZG4ndCByZWFsaXplIG9uIGRhdmluY2kgdGhh
dCB5b3UgYXJlIGxpbWl0ZWQNCg0KSGVyZSBJIGFtIHJlZmVycmluZyB0byB1c2FnZSBvZiBkaXNw
X2NsaywNCg0KRHJpdmVyIGlzIG5vdCBzdXBwb3NlZCB0byBkbyBwbGF0Zm9ybSBoYWNrcyAtIGhl
cmUgeW91IGFyZSB0cnlpbmcgdG8NCmNvbmZpZ3VyZSBzb21ldGhpbmcgKFBMTCkgaW4geW91ciBk
cml2ZXIgd2hpY2ggaXMgbm90IHBhcnQgb2YgTENEQyBJUC4NCkFuZCBMQ0RDIElQIGlzIG5vdCBh
d2FyZSBvZiBQTEwgd2hpY2ggaXMgYSBwbGF0Zm9ybSBzcGVjaWZpYyBtYXR0ZXINCihleGlzdGVu
dCBvbmx5IGluIEFNMzM1eCksIGl0IGlzIG9ubHkgYXdhcmUgb2YgZnVuY3Rpb25hbCBjbG9jay4N
Cg0KWW91IGNhbiBzZXQgdGhlIHJhdGUgb24gImZjayIgKGZ1bmN0aW9uYWwgY2xvY2spIGluIEFN
MzM1eCB1c2luZyBteSBwYXRjaCwNCiJBUk06IEFNMzNYWDogY2xvY2s6IFNFVF9SQVRFX1BBUkVO
VCBpbiBsY2QgcGF0aCIsIGFuZCB0aGVyZQ0Kd291bGQgbm90IGJlIGFueSBuZWVkIGZvciBkcml2
ZXIgdG8gYmUgYXdhcmUgb2YgcGxhdGZvcm0gc3BlY2lmaWMgUExMLg0KDQo+ID4+ID4+ICsgICAg
IHByaXYtPmNsayA9IGNsa19nZXQoZGV2LT5kZXYsICJmY2siKTsNCg0KPiA+PiA+PiArICAgICBw
cml2LT5kaXNwX2NsayA9IGNsa19nZXQoZGV2LT5kZXYsICJkcGxsX2Rpc3BfY2siKTsNCg0KPiBh
dCB0aGUgbW9tZW50IGFsbCB0aGlzIGRpc2N1c3Npb24gaXMgYSBiaXQgbW9vdC4gIEknZCBwcm9w
b3NlIGxlYXZpbmcNCj4gdGhlIGRyaXZlciBhcyBpdCBpcyBmb3Igbm93LCBiZWNhdXNlIHRoYXQg
YXQgbGVhc3QgbWFrZXMgaXQgdXNlZnVsIG9uDQo+IGFtMzN4eC4gIEFuZCB3aGVuIENDRiBhbmQg
ZGF2aW5jaSBoYXZlIHRoZSBuZWVkZWQgc3VwcG9ydCBpbiBwbGFjZSwNCg0KTGV0J3MgZm9yZ2V0
IGFib3V0IGxldmVyYWdpbmcgQ0NGIGluIGRyaXZlciwgYnV0IHNhbmUgc29sdXRpb24gdy5yLnQg
UExMDQpjb25maWd1cmF0aW9uIHdvdWxkIGJlIHRvIGRvIGFzIG1lbnRpb25lZCBhYm92ZS4NCg0K
UmVnYXJkcw0KQWZ6YWwNCg0K

^ permalink raw reply	[flat|nested] 66+ messages in thread

* RE: [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-28  9:56             ` Mohammed, Afzal
  0 siblings, 0 replies; 66+ messages in thread
From: Mohammed, Afzal @ 2013-01-28  9:56 UTC (permalink / raw)
  To: Rob Clark
  Cc: dri-devel, linux-omap, linux-arm-kernel, patches, linux-fbdev,
	Mike Turquette, Nori, Sekhar

Hi Rob,

On Fri, Jan 25, 2013 at 20:22:55, Rob Clark wrote:
> On Fri, Jan 25, 2013 at 8:15 AM, Mohammed, Afzal <afzal@ti.com> wrote:

> > It's not about being simple, but not doing the wrong way, here you are
> > relying on a platform specific clock in a driver, think about the case
> > where same IP is used on another platform. Either way it is not a good
> > thing to handle platform specific details (about disp_clk) in driver.

> Right, but I was trying to understand what was the benefit that the
> added complexity is.  I didn't realize on davinci that you are limited

Here I am referring to usage of disp_clk,

Driver is not supposed to do platform hacks - here you are trying to
configure something (PLL) in your driver which is not part of LCDC IP.
And LCDC IP is not aware of PLL which is a platform specific matter
(existent only in AM335x), it is only aware of functional clock.

You can set the rate on "fck" (functional clock) in AM335x using my patch,
"ARM: AM33XX: clock: SET_RATE_PARENT in lcd path", and there
would not be any need for driver to be aware of platform specific PLL.

> >> >> +     priv->clk = clk_get(dev->dev, "fck");

> >> >> +     priv->disp_clk = clk_get(dev->dev, "dpll_disp_ck");

> at the moment all this discussion is a bit moot.  I'd propose leaving
> the driver as it is for now, because that at least makes it useful on
> am33xx.  And when CCF and davinci have the needed support in place,

Let's forget about leveraging CCF in driver, but sane solution w.r.t PLL
configuration would be to do as mentioned above.

Regards
Afzal


^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-28  9:56             ` Mohammed, Afzal
  0 siblings, 0 replies; 66+ messages in thread
From: Mohammed, Afzal @ 2013-01-28  9:56 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Rob,

On Fri, Jan 25, 2013 at 20:22:55, Rob Clark wrote:
> On Fri, Jan 25, 2013 at 8:15 AM, Mohammed, Afzal <afzal@ti.com> wrote:

> > It's not about being simple, but not doing the wrong way, here you are
> > relying on a platform specific clock in a driver, think about the case
> > where same IP is used on another platform. Either way it is not a good
> > thing to handle platform specific details (about disp_clk) in driver.

> Right, but I was trying to understand what was the benefit that the
> added complexity is.  I didn't realize on davinci that you are limited

Here I am referring to usage of disp_clk,

Driver is not supposed to do platform hacks - here you are trying to
configure something (PLL) in your driver which is not part of LCDC IP.
And LCDC IP is not aware of PLL which is a platform specific matter
(existent only in AM335x), it is only aware of functional clock.

You can set the rate on "fck" (functional clock) in AM335x using my patch,
"ARM: AM33XX: clock: SET_RATE_PARENT in lcd path", and there
would not be any need for driver to be aware of platform specific PLL.

> >> >> +     priv->clk = clk_get(dev->dev, "fck");

> >> >> +     priv->disp_clk = clk_get(dev->dev, "dpll_disp_ck");

> at the moment all this discussion is a bit moot.  I'd propose leaving
> the driver as it is for now, because that at least makes it useful on
> am33xx.  And when CCF and davinci have the needed support in place,

Let's forget about leveraging CCF in driver, but sane solution w.r.t PLL
configuration would be to do as mentioned above.

Regards
Afzal

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
  2013-01-28  9:56             ` Mohammed, Afzal
  (?)
@ 2013-01-28 16:37               ` Rob Clark
  -1 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-28 16:37 UTC (permalink / raw)
  To: Mohammed, Afzal
  Cc: dri-devel, linux-omap, linux-arm-kernel, patches, linux-fbdev,
	Mike Turquette, Nori, Sekhar

On Mon, Jan 28, 2013 at 3:56 AM, Mohammed, Afzal <afzal@ti.com> wrote:
> Hi Rob,
>
> On Fri, Jan 25, 2013 at 20:22:55, Rob Clark wrote:
>> On Fri, Jan 25, 2013 at 8:15 AM, Mohammed, Afzal <afzal@ti.com> wrote:
>
>> > It's not about being simple, but not doing the wrong way, here you are
>> > relying on a platform specific clock in a driver, think about the case
>> > where same IP is used on another platform. Either way it is not a good
>> > thing to handle platform specific details (about disp_clk) in driver.
>
>> Right, but I was trying to understand what was the benefit that the
>> added complexity is.  I didn't realize on davinci that you are limited
>
> Here I am referring to usage of disp_clk,
>
> Driver is not supposed to do platform hacks - here you are trying to
> configure something (PLL) in your driver which is not part of LCDC IP.
> And LCDC IP is not aware of PLL which is a platform specific matter
> (existent only in AM335x), it is only aware of functional clock.
>
> You can set the rate on "fck" (functional clock) in AM335x using my patch,
> "ARM: AM33XX: clock: SET_RATE_PARENT in lcd path", and there
> would not be any need for driver to be aware of platform specific PLL.

right, but I think it would be better to just make another patch that
changes tilcdc to just set rate on fck after that patch is merged.  I
mean, I'd rather have the driver at least usable on AM33xx until then,
rather than broken for everyone.

BR,
-R

>> >> >> +     priv->clk = clk_get(dev->dev, "fck");
>
>> >> >> +     priv->disp_clk = clk_get(dev->dev, "dpll_disp_ck");
>
>> at the moment all this discussion is a bit moot.  I'd propose leaving
>> the driver as it is for now, because that at least makes it useful on
>> am33xx.  And when CCF and davinci have the needed support in place,
>
> Let's forget about leveraging CCF in driver, but sane solution w.r.t PLL
> configuration would be to do as mentioned above.
>
> Regards
> Afzal
>

^ permalink raw reply	[flat|nested] 66+ messages in thread

* Re: [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-28 16:37               ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-28 16:37 UTC (permalink / raw)
  To: Mohammed, Afzal
  Cc: dri-devel, linux-omap, linux-arm-kernel, patches, linux-fbdev,
	Mike Turquette, Nori, Sekhar

On Mon, Jan 28, 2013 at 3:56 AM, Mohammed, Afzal <afzal@ti.com> wrote:
> Hi Rob,
>
> On Fri, Jan 25, 2013 at 20:22:55, Rob Clark wrote:
>> On Fri, Jan 25, 2013 at 8:15 AM, Mohammed, Afzal <afzal@ti.com> wrote:
>
>> > It's not about being simple, but not doing the wrong way, here you are
>> > relying on a platform specific clock in a driver, think about the case
>> > where same IP is used on another platform. Either way it is not a good
>> > thing to handle platform specific details (about disp_clk) in driver.
>
>> Right, but I was trying to understand what was the benefit that the
>> added complexity is.  I didn't realize on davinci that you are limited
>
> Here I am referring to usage of disp_clk,
>
> Driver is not supposed to do platform hacks - here you are trying to
> configure something (PLL) in your driver which is not part of LCDC IP.
> And LCDC IP is not aware of PLL which is a platform specific matter
> (existent only in AM335x), it is only aware of functional clock.
>
> You can set the rate on "fck" (functional clock) in AM335x using my patch,
> "ARM: AM33XX: clock: SET_RATE_PARENT in lcd path", and there
> would not be any need for driver to be aware of platform specific PLL.

right, but I think it would be better to just make another patch that
changes tilcdc to just set rate on fck after that patch is merged.  I
mean, I'd rather have the driver at least usable on AM33xx until then,
rather than broken for everyone.

BR,
-R

>> >> >> +     priv->clk = clk_get(dev->dev, "fck");
>
>> >> >> +     priv->disp_clk = clk_get(dev->dev, "dpll_disp_ck");
>
>> at the moment all this discussion is a bit moot.  I'd propose leaving
>> the driver as it is for now, because that at least makes it useful on
>> am33xx.  And when CCF and davinci have the needed support in place,
>
> Let's forget about leveraging CCF in driver, but sane solution w.r.t PLL
> configuration would be to do as mentioned above.
>
> Regards
> Afzal
>

^ permalink raw reply	[flat|nested] 66+ messages in thread

* [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3)
@ 2013-01-28 16:37               ` Rob Clark
  0 siblings, 0 replies; 66+ messages in thread
From: Rob Clark @ 2013-01-28 16:37 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Jan 28, 2013 at 3:56 AM, Mohammed, Afzal <afzal@ti.com> wrote:
> Hi Rob,
>
> On Fri, Jan 25, 2013 at 20:22:55, Rob Clark wrote:
>> On Fri, Jan 25, 2013 at 8:15 AM, Mohammed, Afzal <afzal@ti.com> wrote:
>
>> > It's not about being simple, but not doing the wrong way, here you are
>> > relying on a platform specific clock in a driver, think about the case
>> > where same IP is used on another platform. Either way it is not a good
>> > thing to handle platform specific details (about disp_clk) in driver.
>
>> Right, but I was trying to understand what was the benefit that the
>> added complexity is.  I didn't realize on davinci that you are limited
>
> Here I am referring to usage of disp_clk,
>
> Driver is not supposed to do platform hacks - here you are trying to
> configure something (PLL) in your driver which is not part of LCDC IP.
> And LCDC IP is not aware of PLL which is a platform specific matter
> (existent only in AM335x), it is only aware of functional clock.
>
> You can set the rate on "fck" (functional clock) in AM335x using my patch,
> "ARM: AM33XX: clock: SET_RATE_PARENT in lcd path", and there
> would not be any need for driver to be aware of platform specific PLL.

right, but I think it would be better to just make another patch that
changes tilcdc to just set rate on fck after that patch is merged.  I
mean, I'd rather have the driver at least usable on AM33xx until then,
rather than broken for everyone.

BR,
-R

>> >> >> +     priv->clk = clk_get(dev->dev, "fck");
>
>> >> >> +     priv->disp_clk = clk_get(dev->dev, "dpll_disp_ck");
>
>> at the moment all this discussion is a bit moot.  I'd propose leaving
>> the driver as it is for now, because that at least makes it useful on
>> am33xx.  And when CCF and davinci have the needed support in place,
>
> Let's forget about leveraging CCF in driver, but sane solution w.r.t PLL
> configuration would be to do as mentioned above.
>
> Regards
> Afzal
>

^ permalink raw reply	[flat|nested] 66+ messages in thread

end of thread, other threads:[~2013-01-28 16:37 UTC | newest]

Thread overview: 66+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-01-22 22:36 [PATCH 0/4] TI LCDC DRM driver Rob Clark
2013-01-22 22:36 ` Rob Clark
2013-01-22 22:36 ` [PATCH 1/4] drm/tilcdc: add TI LCD Controller DRM driver (v3) Rob Clark
2013-01-22 22:36   ` Rob Clark
2013-01-22 23:41   ` Daniel Vetter
2013-01-22 23:41     ` Daniel Vetter
2013-01-23  7:43   ` Koen Kooi
2013-01-23  7:43     ` Koen Kooi
2013-01-23  9:42   ` Jean-Francois Moine
2013-01-23  9:42     ` Jean-Francois Moine
2013-01-23 13:24     ` Rob Clark
2013-01-23 13:24       ` Rob Clark
2013-01-23 13:36       ` Russell King - ARM Linux
2013-01-23 13:36         ` Russell King - ARM Linux
2013-01-23 14:13         ` Rob Clark
2013-01-23 14:13           ` Rob Clark
2013-01-23 14:37           ` Rob Clark
2013-01-23 14:37             ` Rob Clark
2013-01-25 13:19   ` Mohammed, Afzal
2013-01-25 13:19     ` Mohammed, Afzal
2013-01-25 13:19     ` Mohammed, Afzal
2013-01-25 13:59     ` Rob Clark
2013-01-25 13:59       ` Rob Clark
2013-01-25 13:59       ` Rob Clark
2013-01-25 14:15       ` Mohammed, Afzal
2013-01-25 14:15         ` Mohammed, Afzal
2013-01-25 14:15         ` Mohammed, Afzal
2013-01-25 14:52         ` Rob Clark
2013-01-25 14:52           ` Rob Clark
2013-01-25 14:52           ` Rob Clark
2013-01-28  9:56           ` Mohammed, Afzal
2013-01-28  9:56             ` Mohammed, Afzal
2013-01-28  9:56             ` Mohammed, Afzal
2013-01-28 16:37             ` Rob Clark
2013-01-28 16:37               ` Rob Clark
2013-01-28 16:37               ` Rob Clark
2013-01-22 22:36 ` [PATCH 2/4] drm/i2c: nxp-tda998x (v2) Rob Clark
2013-01-22 22:36   ` Rob Clark
2013-01-23  7:44   ` Koen Kooi
2013-01-23  7:44     ` Koen Kooi
2013-01-23  9:42   ` Jean-Francois Moine
2013-01-23  9:42     ` Jean-Francois Moine
2013-01-23 13:25     ` Rob Clark
2013-01-23 13:25       ` Rob Clark
2013-01-24 11:57   ` Daniel Vetter
2013-01-24 11:57     ` Daniel Vetter
2013-01-24 14:10     ` Rob Clark
2013-01-24 14:10       ` Rob Clark
2013-01-22 22:36 ` [PATCH 3/4] drm/tilcdc: add encoder slave Rob Clark
2013-01-22 22:36   ` Rob Clark
2013-01-24 12:43   ` Daniel Vetter
2013-01-24 12:43     ` Daniel Vetter
2013-01-24 14:26     ` Rob Clark
2013-01-24 14:26       ` Rob Clark
2013-01-24 13:01   ` Daniel Vetter
2013-01-24 13:01     ` Daniel Vetter
2013-01-24 14:31     ` Rob Clark
2013-01-24 14:31       ` Rob Clark
2013-01-22 22:36 ` [PATCH 4/4] drm/tilcdc: add support for LCD panels (v4) Rob Clark
2013-01-22 22:36   ` Rob Clark
2013-01-24 13:08   ` Daniel Vetter
2013-01-24 13:08     ` Daniel Vetter
2013-01-24 14:40     ` Rob Clark
2013-01-24 14:40       ` Rob Clark
2013-01-23  7:48 ` [PATCH 0/4] TI LCDC DRM driver Sascha Hauer
2013-01-23  7:48   ` Sascha Hauer

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.