[PATCH v1 07/19] drm: sti: add TVOut driver

Benjamin Gaignard benjamin.gaignard at linaro.org
Tue Apr 8 05:19:14 PDT 2014


TVout hardware block is responsible to dispatch the data flow coming
from compositor block to any of the output (HDMI or Analog TV).
It control when output are start/stop and configure according the
require flow path.

Signed-off-by: Benjamin Gaignard <benjamin.gaignard at linaro.org>
Signed-off-by: Vincent Abriou <vincent.abriou at st.com>
Signed-off-by: Fabien Dessenne <fabien.dessenne at st.com>
---
 drivers/gpu/drm/sti/Makefile    |   4 +-
 drivers/gpu/drm/sti/sti_hda.c   | 373 ++++++++++++++++++++++
 drivers/gpu/drm/sti/sti_hda.h   |  14 +
 drivers/gpu/drm/sti/sti_hdmi.c  | 542 +++++++++++++++++++++++++++++++
 drivers/gpu/drm/sti/sti_hdmi.h  |   3 +
 drivers/gpu/drm/sti/sti_tvout.c | 682 ++++++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/sti/sti_tvout.h | 105 +++++++
 7 files changed, 1722 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/sti/sti_hda.h
 create mode 100644 drivers/gpu/drm/sti/sti_tvout.c
 create mode 100644 drivers/gpu/drm/sti/sti_tvout.h

diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile
index 134ae6d..447eccf 100644
--- a/drivers/gpu/drm/sti/Makefile
+++ b/drivers/gpu/drm/sti/Makefile
@@ -1,6 +1,8 @@
 ccflags-y := -Iinclude/drm
 
-stidrm-y := sti_hdmi.o \
+stidrm-y := \
+	sti_tvout.o \
+	sti_hdmi.o \
 	sti_hdmi_tx3g0c55phy.o \
 	sti_hdmi_tx3g4c28phy.o \
 	sti_hda.o \
diff --git a/drivers/gpu/drm/sti/sti_hda.c b/drivers/gpu/drm/sti/sti_hda.c
index 03ed120..02a6f3a 100644
--- a/drivers/gpu/drm/sti/sti_hda.c
+++ b/drivers/gpu/drm/sti/sti_hda.c
@@ -10,6 +10,8 @@
 
 #include <drm/drmP.h>
 
+#include "sti_hda.h"
+
 /* HDformatter registers */
 #define HDA_ANA_CFG                     0x0000
 #define HDA_ANA_SCALE_CTRL_Y            0x0004
@@ -371,6 +373,377 @@ static int sti_hda_get_modes(struct drm_connector *drm_connector)
 	return count;
 }
 
+/*
+ * Start hd analog
+ *
+ * @connector: pointer on the tvout HD analog connector
+ *
+ * Return 0 on success
+ */
+static int sti_hda_start(struct sti_tvout_connector *connector)
+{
+	struct sti_hda *hda = (struct sti_hda *)connector->priv;
+	u32 val, i, mode_idx;
+	u32 src_filter_y, src_filter_c;
+	u32 *coef_y, *coef_c;
+	u32 filter_mode;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	/* Prepare/enable clocks */
+	if (clk_prepare_enable(hda->clk_pix))
+		DRM_ERROR("Failed to prepare/enable hda_pix clk\n");
+	if (clk_prepare_enable(hda->clk_hddac))
+		DRM_ERROR("Failed to prepare/enable hda_hddac clk\n");
+
+	hda->enabled = true;
+
+	if (!hda_get_mode_idx(hda->mode, &mode_idx)) {
+		DRM_ERROR("Undefined mode\n");
+		return 1;
+	}
+
+	switch (hda_supported_modes[mode_idx].vid_cat) {
+	case VID_HD_148M:
+		DRM_ERROR("Beyond HD analog capabilities\n");
+		return 1;
+	case VID_HD_74M:
+		/* HD use alternate 2x filter */
+		filter_mode = CFG_AWG_FLTR_MODE_HD;
+		src_filter_y = HDA_ANA_SRC_Y_CFG_ALT_2X;
+		src_filter_c = HDA_ANA_SRC_C_CFG_ALT_2X;
+		coef_y = coef_y_alt_2x;
+		coef_c = coef_c_alt_2x;
+		break;
+	case VID_ED:
+		/* ED uses 4x filter */
+		filter_mode = CFG_AWG_FLTR_MODE_ED;
+		src_filter_y = HDA_ANA_SRC_Y_CFG_4X;
+		src_filter_c = HDA_ANA_SRC_C_CFG_4X;
+		coef_y = coef_yc_4x;
+		coef_c = coef_yc_4x;
+		break;
+	case VID_SD:
+		DRM_ERROR("Not supported\n");
+		return 1;
+	default:
+		DRM_ERROR("Undefined resolution\n");
+		return 1;
+	}
+	DRM_DEBUG_DRIVER("Using HDA mode #%d\n", mode_idx);
+
+	/* Enable HD Video DACs */
+	hda_enable_hd_dacs(hda, true);
+
+	/* Configure scaler */
+	writel(SCALE_CTRL_Y_DFLT, hda->regs + HDA_ANA_SCALE_CTRL_Y);
+	writel(SCALE_CTRL_CB_DFLT, hda->regs + HDA_ANA_SCALE_CTRL_CB);
+	writel(SCALE_CTRL_CR_DFLT, hda->regs + HDA_ANA_SCALE_CTRL_CR);
+
+	/* Configure sampler */
+	writel(src_filter_y, hda->regs + HDA_ANA_SRC_Y_CFG);
+	writel(src_filter_c, hda->regs + HDA_ANA_SRC_C_CFG);
+	for (i = 0; i < SAMPLER_COEF_NB; i++) {
+		writel(coef_y[i], hda->regs + HDA_COEFF_Y_PH1_TAP123 + i * 4);
+		writel(coef_c[i], hda->regs + HDA_COEFF_C_PH1_TAP123 + i * 4);
+	}
+
+	/* Configure main HDFormatter */
+	val = 0;
+	val |= (hda->mode.flags & DRM_MODE_FLAG_INTERLACE) ?
+	    0 : CFG_AWG_ASYNC_VSYNC_MTD;
+	val |= (CFG_PBPR_SYNC_OFF_VAL << CFG_PBPR_SYNC_OFF_SHIFT);
+	val |= filter_mode;
+	writel(val, hda->regs + HDA_ANA_CFG);
+
+	/* Configure AWG */
+	sti_hda_configure_awg(hda, hda_supported_modes[mode_idx].awg_instr,
+			      hda_supported_modes[mode_idx].nb_instr);
+
+	/* Enable AWG */
+	hda_reg_writemask(hda->regs + HDA_ANA_CFG, 1, CFG_AWG_ASYNC_EN);
+
+	return 0;
+}
+
+/*
+ * Stop HD analog
+ *
+ * @connector: pointer on the tvout HD analog connector
+ */
+static void sti_hda_stop(struct sti_tvout_connector *connector)
+{
+	struct sti_hda *hda = (struct sti_hda *)connector->priv;
+
+	if (!hda->enabled)
+		return;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	/* Disable HD DAC and AWG */
+	hda_reg_writemask(hda->regs + HDA_ANA_CFG, 0, CFG_AWG_ASYNC_EN);
+	hda_enable_hd_dacs(hda, false);
+
+	/* Disable/unprepare hda clock */
+	clk_disable_unprepare(hda->clk_hddac);
+	clk_disable_unprepare(hda->clk_pix);
+
+	hda->enabled = false;
+}
+
+/*
+ * Check if the drm display mode in supported by the HD analog
+ *
+ * @connector: pointer on the tvout HD analog connector
+ * @mode: drm display mode
+ *
+ * Return 0 if supported
+ */
+#define CLK_TOLERANCE_HZ 50
+static int sti_hda_check_mode(struct sti_tvout_connector *connector,
+			      struct drm_display_mode *mode)
+{
+	struct sti_hda *hda = (struct sti_hda *)connector->priv;
+	int target = mode->clock * 1000;
+	int target_min = target - CLK_TOLERANCE_HZ;
+	int target_max = target + CLK_TOLERANCE_HZ;
+	int result;
+	int idx;
+
+	if (!hda_get_mode_idx(*mode, &idx)) {
+		return 1;
+	} else {
+		result = clk_round_rate(hda->clk_pix, target);
+
+		DRM_DEBUG_DRIVER("target rate = %d => available rate = %d\n",
+				 target, result);
+
+		if ((result < target_min) || (result > target_max)) {
+			DRM_DEBUG_DRIVER("hda pixclk=%d not supported\n",
+					 target);
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Set the drm display mode in the local structure
+ *
+ * @connector: pointer on the tvout HD analog connector
+ * @mode: drm display mode
+ *
+ * Return 0 on success
+ */
+static int sti_hda_set_mode(struct sti_tvout_connector *connector,
+			    struct drm_display_mode *mode)
+{
+	struct sti_hda *hda = (struct sti_hda *)connector->priv;
+	u32 mode_idx;
+	int hddac_rate;
+	int ret;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	memcpy(&hda->mode, mode, sizeof(struct drm_display_mode));
+
+	if (!hda_get_mode_idx(hda->mode, &mode_idx)) {
+		DRM_ERROR("Undefined mode\n");
+		return 1;
+	}
+
+	switch (hda_supported_modes[mode_idx].vid_cat) {
+	case VID_HD_74M:
+		/* HD use alternate 2x filter */
+		hddac_rate = mode->clock * 1000 * 2;
+		break;
+	case VID_ED:
+		/* ED uses 4x filter */
+		hddac_rate = mode->clock * 1000 * 4;
+		break;
+	default:
+		DRM_ERROR("Undefined mode\n");
+		return 1;
+	}
+
+	/* HD DAC = 148.5Mhz or 108 Mhz */
+	ret = clk_set_rate(hda->clk_hddac, hddac_rate);
+	if (ret < 0) {
+		DRM_ERROR("Cannot set rate (%dHz) for hda_hddac clk\n",
+			  hddac_rate);
+		return ret;
+	}
+
+	/* HDformatter clock = compositor clock */
+	ret = clk_set_rate(hda->clk_pix, mode->clock * 1000);
+	if (ret < 0) {
+		DRM_ERROR("Cannot set rate (%dHz) for hda_pix clk\n",
+			  mode->clock * 1000);
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * Detect if HD analog is connected
+ *
+ * @connector: pointer on the tvout HD analog connector
+ *
+ * Return true if HD analog cable is connected which is assumed to be always
+ * the case
+ */
+static bool sti_hda_detect(struct sti_tvout_connector *connector)
+{
+	DRM_DEBUG_DRIVER("\n");
+
+	return true;
+}
+
+/*
+ * Check if HD analog is enabled
+ *
+ * @connector: pointer on the tvout HD analog connector
+ *
+ * Return true if HD analog is enabled
+ */
+static bool sti_hda_is_enabled(struct sti_tvout_connector *connector)
+{
+	struct sti_hda *hda = (struct sti_hda *)connector->priv;
+
+	return hda->enabled;
+}
+
+/*
+ * Prepare/configure HD analog
+ *
+ * @connector: pointer on the tvout HD analog connector
+ */
+static void sti_hda_prepare(struct sti_tvout_connector *connector)
+{
+	struct sti_hda *hda = (struct sti_hda *)connector->priv;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	/* reset HDF  */
+	writel(0x00000000, hda->regs + HDA_ANA_CFG);
+	writel(0x00000000, hda->regs + HDA_ANA_ANC_CTRL);
+}
+
+/*
+ * Debugfs
+ */
+
+#define HDA_DBG_DUMP(reg) seq_printf(m, "\n %-25s 0x%08X", #reg, \
+		readl(hda->regs + reg))
+
+static void hda_dbg_cfg(struct seq_file *m, int val)
+{
+	seq_puts(m, "\t AWG ");
+	seq_puts(m, val & CFG_AWG_ASYNC_EN ? "enabled" : "disabled");
+}
+
+static void hda_dbg_awgi(struct seq_file *m, void __iomem *reg)
+{
+	int i;
+
+	seq_puts(m, "\n HDA_SYNC_AWGI             ");
+	for (i = 0; i < 10; i++)
+		seq_printf(m, "%04X ", readl(reg + i * 4));
+	seq_puts(m, "...");
+}
+
+static void hda_dbg_video_dacs_ctrl(struct seq_file *m, void __iomem *reg)
+{
+	u32 val = readl(reg);
+	u32 mask;
+
+	switch ((u32)reg & VIDEO_DACS_CONTROL_MASK) {
+	case VIDEO_DACS_CONTROL_SYSCFG2535:
+		mask = DAC_CFG_HD_OFF_MASK;
+		break;
+	case VIDEO_DACS_CONTROL_SYSCFG5072:
+		mask = DAC_CFG_HD_HZUVW_OFF_MASK;
+		break;
+	default:
+		DRM_INFO("Video DACS control register not supported!");
+		return;
+	}
+
+	seq_puts(m, "\n");
+
+	seq_printf(m, "\n %-25s 0x%08X", "VIDEO_DACS_CONTROL", val);
+	seq_puts(m, "\tHD DACs ");
+	seq_puts(m, val & mask ? "disabled" : "enabled");
+}
+
+static void sti_hda_dbg_show(struct sti_tvout_connector *connector,
+			     struct seq_file *m)
+{
+	struct sti_hda *hda = (struct sti_hda *)connector->priv;
+
+	seq_puts(m, "\n");
+	seq_printf(m, "\nHD Analog: (virt base addr = 0x%p)", hda->regs);
+	HDA_DBG_DUMP(HDA_ANA_CFG);
+	hda_dbg_cfg(m, readl(hda->regs + HDA_ANA_CFG));
+	HDA_DBG_DUMP(HDA_ANA_SCALE_CTRL_Y);
+	HDA_DBG_DUMP(HDA_ANA_SCALE_CTRL_CB);
+	HDA_DBG_DUMP(HDA_ANA_SCALE_CTRL_CR);
+	HDA_DBG_DUMP(HDA_ANA_ANC_CTRL);
+	HDA_DBG_DUMP(HDA_ANA_SRC_Y_CFG);
+	HDA_DBG_DUMP(HDA_ANA_SRC_C_CFG);
+	hda_dbg_awgi(m, hda->regs + HDA_SYNC_AWGI);
+	if (hda->video_dacs_ctrl)
+		hda_dbg_video_dacs_ctrl(m, hda->video_dacs_ctrl);
+}
+
+/*
+ * create the HD analog output
+ *
+ * @tvout: pointer on the tvout information
+ *
+ * Return pointer on the created tvout connector or NULL if error occurs
+ */
+struct sti_tvout_connector *sti_hda_create(struct sti_tvout *tvout)
+{
+	struct sti_hda *hda = container_of(hda_dev, struct sti_hda, dev);
+	struct device *dev = &hda->dev;
+	struct sti_tvout_connector *connector;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	if (!hda) {
+		DRM_INFO("%s: No hda device probed\n", __func__);
+		return NULL;
+	}
+
+	connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL);
+	if (!connector) {
+		DRM_ERROR("Failed to allocate memory for connector\n");
+		goto connector_alloc_failed;
+	}
+
+	/* Set the drm device handle */
+	hda->drm_dev = tvout->drm_dev;
+
+	connector->priv = (void *)hda;
+	connector->start = sti_hda_start;
+	connector->stop = sti_hda_stop;
+	connector->get_modes = sti_hda_get_modes;
+	connector->check_mode = sti_hda_check_mode;
+	connector->set_mode = sti_hda_set_mode;
+	connector->detect = sti_hda_detect;
+	connector->is_enabled = sti_hda_is_enabled;
+	connector->prepare = sti_hda_prepare;
+	connector->dbg_show = sti_hda_dbg_show;
+
+	return connector;
+
+connector_alloc_failed:
+	return NULL;
+}
+
 static int sti_hda_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
diff --git a/drivers/gpu/drm/sti/sti_hda.h b/drivers/gpu/drm/sti/sti_hda.h
new file mode 100644
index 0000000..e88b30e
--- /dev/null
+++ b/drivers/gpu/drm/sti/sti_hda.h
@@ -0,0 +1,14 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2014
+ * Authors: Fabien Dessenne <fabien.dessenne at st.com> for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#ifndef _STI_HDA_H_
+#define _STI_HDA_H_
+
+#include "sti_tvout.h"
+
+struct sti_tvout_connector *sti_hda_create(struct sti_tvout *tvout);
+
+#endif
diff --git a/drivers/gpu/drm/sti/sti_hdmi.c b/drivers/gpu/drm/sti/sti_hdmi.c
index 5bbee6b..99e2be5 100644
--- a/drivers/gpu/drm/sti/sti_hdmi.c
+++ b/drivers/gpu/drm/sti/sti_hdmi.c
@@ -379,6 +379,548 @@ fail:
 	return -1;
 }
 
+/*
+ * Start hdmi
+ *
+ * @connector: pointer on the tvout hdmi connector
+ *
+ * Return -1 if error occurs
+ */
+static int sti_hdmi_start(struct sti_tvout_connector *connector)
+{
+	struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv;
+	int ret = 0;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	/* Prepare/enable clocks */
+	if (clk_prepare_enable(hdmi->clk_pix))
+		DRM_ERROR("Failed to prepare/enable hdmi_pix clk\n");
+	if (clk_prepare_enable(hdmi->clk_tmds))
+		DRM_ERROR("Failed to prepare/enable hdmi_tmds clk\n");
+	if (clk_prepare_enable(hdmi->clk_phy))
+		DRM_ERROR("Failed to prepare/enable hdmi_rejec_pll clk\n");
+
+	hdmi->enabled = true;
+
+	/* Program hdmi serializer and start phy */
+	ret = hdmi_phy_start(hdmi);
+	if (ret) {
+		DRM_ERROR("Unable to start hdmi phy\n");
+		return ret;
+	}
+
+	/* Program hdmi active area */
+	hdmi_active_area(hdmi);
+
+	/* Enable working interrupts */
+	writel(HDMI_WORKING_INT, hdmi->regs + HDMI_INT_EN);
+
+	/* Program hdmi config */
+	hdmi_config(hdmi);
+
+	/* Program AVI infoframe */
+	ret = hdmi_avi_infoframe_config(hdmi);
+	if (ret)
+		DRM_ERROR("Unable to configure AVI infoframe\n");
+
+	/* Sw reset */
+	ret = hdmi_swreset(hdmi);
+	if (ret)
+		DRM_ERROR("Unable to perform the hdmi sw reset\n");
+
+	return ret;
+}
+
+/*
+ * Stop hdmi
+ *
+ * @connector: pointer on the tvout hdmi connector
+ */
+static void sti_hdmi_stop(struct sti_tvout_connector *connector)
+{
+	struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv;
+	u32 val;
+	u32 mask;
+
+	if (!hdmi->enabled)
+		return;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	/* Disable HDMI */
+	mask = HDMI_CFG_DEVICE_EN;
+	val = ~HDMI_CFG_DEVICE_EN;
+
+	hdmi_reg_writemask(hdmi->regs + HDMI_CFG, val, mask);
+
+	/* Stop the phy */
+	hdmi_phy_stop(hdmi);
+
+	/* Disable/unprepare hdmi clock */
+	clk_disable_unprepare(hdmi->clk_phy);
+	clk_disable_unprepare(hdmi->clk_tmds);
+	clk_disable_unprepare(hdmi->clk_pix);
+
+	hdmi->enabled = false;
+}
+
+/*
+ * Check if the drm display mode in supported by the hdmi
+ *
+ * @connector: pointer on the tvout hdmi connector
+ * @mode: drm display mode
+ *
+ * Return -1 if not supported
+ */
+#define CLK_TOLERANCE_HZ 50
+static int sti_hdmi_check_mode(struct sti_tvout_connector *connector,
+			       struct drm_display_mode *mode)
+{
+	struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv;
+	int target = mode->clock * 1000;
+	int target_min = target - CLK_TOLERANCE_HZ;
+	int target_max = target + CLK_TOLERANCE_HZ;
+	int result;
+
+	result = clk_round_rate(hdmi->clk_pix, target);
+
+	DRM_DEBUG_DRIVER("target rate = %d => available rate = %d\n",
+			 target, result);
+
+	if ((result < target_min) || (result > target_max)) {
+		DRM_DEBUG_DRIVER("hdmi pixclk=%d not supported\n", target);
+		return -1;
+	}
+
+	return 0;
+}
+
+/*
+ * Set the drm display mode in the local structure
+ *
+ * @connector: pointer on the tvout hdmi connector
+ * @mode: drm display mode
+ *
+ * Return -1 if error occurs
+ */
+/* FS bits */
+static int sti_hdmi_set_mode(struct sti_tvout_connector *connector,
+			     struct drm_display_mode *mode)
+{
+	struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv;
+	int ret;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	/* Copy the drm display mode in the connector local structure */
+	memcpy(&hdmi->mode, mode, sizeof(struct drm_display_mode));
+
+	/* Update clock framerate according to the selected mode */
+	ret = clk_set_rate(hdmi->clk_pix, mode->clock * 1000);
+	if (ret < 0) {
+		DRM_ERROR("Cannot set rate (%dHz) for hdmi_pix clk\n",
+			  mode->clock * 1000);
+		return ret;
+	}
+	ret = clk_set_rate(hdmi->clk_phy, mode->clock * 1000);
+	if (ret < 0) {
+		DRM_ERROR("Cannot set rate (%dHz) for hdmi_rejection_pll clk\n",
+			  mode->clock * 1000);
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * Detect if hdmi is connected
+ *
+ * @connector: pointer on the tvout hdmi connector
+ *
+ * Return true if hdmi cable is connected
+ */
+static bool sti_hdmi_detect(struct sti_tvout_connector *connector)
+{
+	struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	if (hdmi->hpd) {
+		DRM_DEBUG_DRIVER("hdmi cable connected\n");
+		return true;
+	} else
+		DRM_DEBUG_DRIVER("hdmi cable disconnected\n");
+
+	return false;
+}
+
+/*
+ * Check if hdmi is enabled
+ *
+ * @connector: pointer on the tvout hdmi connector
+ *
+ * Return true if hdmi is enabled
+ */
+static bool sti_hdmi_is_enabled(struct sti_tvout_connector *connector)
+{
+	struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv;
+
+	return hdmi->enabled;
+}
+
+/*
+ * Prepare/configure hdmi
+ *
+ * @connector: pointer on the tvout hdmi connector
+ */
+static void sti_hdmi_prepare(struct sti_tvout_connector *connector)
+{
+	struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	/* HDMI initialisation */
+	writel(0x00000000, hdmi->regs + HDMI_CFG);
+	writel(0xffffffff, hdmi->regs + HDMI_INT_CLR);
+
+	/* Ensure the PHY is completely powered down */
+	hdmi_phy_stop(hdmi);
+
+	/* Set the default channel data to be a dark red */
+	writel(0x0000, hdmi->regs + HDMI_DFLT_CHL0_DAT);
+	writel(0x0000, hdmi->regs + HDMI_DFLT_CHL1_DAT);
+	writel(0x0060, hdmi->regs + HDMI_DFLT_CHL2_DAT);
+}
+
+/*
+ * Debugfs
+ */
+#define HDMI_DBG_DUMP(reg) seq_printf(m, "\n %-25s 0x%08X", #reg, \
+		readl(hdmi->regs + reg))
+#define HDMI_DBG_DUMP_DI(reg, slot) HDMI_DBG_DUMP(reg(slot))
+#define MAX_STRING_LENGTH 40
+
+static void hdmi_dbg_cfg(struct seq_file *m, int val)
+{
+	int tmp;
+	char str[MAX_STRING_LENGTH];
+	static const char *const mode[] = { "DVI", "HDMI" };
+	static const char *const enable[] = { "disable", "enable" };
+	static const char *const oess_ess[] = { "OESS enable", "ESS enable" };
+	static const char *const polarity[] = { "normal", "inverted" };
+
+	seq_puts(m, "\t");
+
+	tmp = (val & HDMI_CFG_HDMI_NOT_DVI) >> HDMI_CFG_HDMI_NOT_DVI_SHIFT;
+	snprintf(str, MAX_STRING_LENGTH, "mode: %s", mode[tmp]);
+	seq_printf(m, "%-40s", str);
+
+	tmp = (val & HDMI_CFG_HDCP_EN) >> HDMI_CFG_HDCP_EN_SHIFT;
+	snprintf(str, MAX_STRING_LENGTH, "HDCP: %s", enable[tmp]);
+	seq_printf(m, "%-40s", str);
+
+	tmp = (val & HDMI_CFG_ESS_NOT_OESS) >> HDMI_CFG_ESS_NOT_OESS_SHIFT;
+	snprintf(str, MAX_STRING_LENGTH, "HDCP mode: %s", oess_ess[tmp]);
+	seq_printf(m, "%-40s", str);
+
+	seq_printf(m, "\n%-40s", "");
+
+	tmp = (val & HDMI_CFG_SINK_TERM_DET_EN) >>
+	    HDMI_CFG_SINK_TERM_DET_EN_SHIFT;
+	snprintf(str, MAX_STRING_LENGTH,
+		 "Sink term detection: %s", enable[tmp]);
+	seq_printf(m, "%-40s", str);
+
+	tmp = (val & HDMI_CFG_H_SYNC_POL_NEG) >> HDMI_CFG_H_SYNC_POL_NEG_SHIFT;
+	snprintf(str, MAX_STRING_LENGTH, "Hsync polarity: %s", polarity[tmp]);
+	seq_printf(m, "%-40s", str);
+
+	tmp = (val & HDMI_CFG_V_SYNC_POL_NEG) >> HDMI_CFG_V_SYNC_POL_NEG_SHIFT;
+	snprintf(str, MAX_STRING_LENGTH, "Vsync polarity: %s", polarity[tmp]);
+	seq_printf(m, "%-40s", str);
+
+	seq_printf(m, "\n%-40s", "");
+
+	tmp = (val & HDMI_CFG_422_EN) >> HDMI_CFG_422_EN_SHIFT;
+	snprintf(str, MAX_STRING_LENGTH, "YUV422 format: %s", enable[tmp]);
+	seq_printf(m, "%-40s", str);
+}
+
+static void hdmi_dbg_sta(struct seq_file *m, int val)
+{
+	int tmp;
+	char str[MAX_STRING_LENGTH];
+	static const char *const sink_term[] = { "not present", "present" };
+	static const char *const pll_lck[] = { "not locked", "locked" };
+	static const char *const hot_plug[] = { "not connected", "connected" };
+
+	seq_puts(m, "\t");
+
+	tmp = (val & HDMI_STA_FIFO_SAMPLES) >> HDMI_STA_FIFO_SAMPLES_SHIFT;
+	snprintf(str, MAX_STRING_LENGTH, "fifo: %d samples", tmp);
+	seq_printf(m, "%-40s", str);
+
+	tmp = (val & HDMI_STA_SINK_TERM) >> HDMI_STA_SINK_TERM_SHIFT;
+	snprintf(str, MAX_STRING_LENGTH, "sink term: %s", sink_term[tmp]);
+	seq_printf(m, "%-40s", str);
+
+	tmp = (val & HDMI_STA_DLL_LCK) >> HDMI_STA_DLL_LCK_SHIFT;
+	snprintf(str, MAX_STRING_LENGTH, "pll: %s", pll_lck[tmp]);
+	seq_printf(m, "%-40s", str);
+
+	seq_printf(m, "\n%-40s", "");
+
+	tmp = (val & HDMI_STA_HOT_PLUG) >> HDMI_STA_HOT_PLUG_SHIFT;
+	snprintf(str, MAX_STRING_LENGTH, "hdmi cable: %s", hot_plug[tmp]);
+	seq_printf(m, "%-40s", str);
+}
+
+static void hdmi_dbg_sw_di_cfg(struct seq_file *m, int val)
+{
+	int tmp;
+	char str[MAX_STRING_LENGTH];
+	static const char *const en_di[] = { "no transmission",
+		"single transmission", "once every field", "once every frame"
+	};
+
+	seq_puts(m, "\t");
+
+	tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 1));
+	snprintf(str, MAX_STRING_LENGTH, "Data island 1: %s", en_di[tmp]);
+	seq_printf(m, "%-40s", str);
+
+	tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 2)) >> 4;
+	snprintf(str, MAX_STRING_LENGTH, "Data island 2: %s", en_di[tmp]);
+	seq_printf(m, "%-40s", str);
+
+	tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 3)) >> 8;
+	snprintf(str, MAX_STRING_LENGTH, "Data island 3: %s", en_di[tmp]);
+	seq_printf(m, "%-40s", str);
+
+	seq_printf(m, "\n%-40s", "");
+
+	tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 4)) >> 12;
+	snprintf(str, MAX_STRING_LENGTH, "Data island 4: %s", en_di[tmp]);
+	seq_printf(m, "%-40s", str);
+
+	tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 5)) >> 16;
+	snprintf(str, MAX_STRING_LENGTH, "Data island 5: %s", en_di[tmp]);
+	seq_printf(m, "%-40s", str);
+
+	tmp = (val & HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, 6)) >> 20;
+	snprintf(str, MAX_STRING_LENGTH, "Data island 6: %s", en_di[tmp]);
+	seq_printf(m, "%-40s", str);
+}
+
+static void hdmi_dbg_xmin(struct seq_file *m, int val)
+{
+	seq_printf(m, "\tXmin: %4d", val & 0x0FFF);
+}
+
+static void hdmi_dbg_xmax(struct seq_file *m, int val)
+{
+	seq_printf(m, "\tXmax: %4d", val & 0x0FFF);
+}
+
+static void hdmi_dbg_ymin(struct seq_file *m, int val)
+{
+	seq_printf(m, "\tYmin: %4d", val & 0x0FFF);
+}
+
+static void hdmi_dbg_ymax(struct seq_file *m, int val)
+{
+	seq_printf(m, "\tYmax: %4d", val & 0x0FFF);
+}
+
+static void sti_hdmi_dbg_show(struct sti_tvout_connector *connector,
+			      struct seq_file *m)
+{
+	struct sti_hdmi *hdmi = (struct sti_hdmi *)connector->priv;
+	struct drm_display_mode *mode = &hdmi->mode;
+	struct hdmi_avi_infoframe info;
+	char str[MAX_STRING_LENGTH];
+
+	seq_puts(m, "\n");
+	seq_printf(m, "\nHDMI: (virt base addr = 0x%p)", hdmi->regs);
+	HDMI_DBG_DUMP(HDMI_CFG);
+	hdmi_dbg_cfg(m, readl(hdmi->regs + HDMI_CFG));
+	HDMI_DBG_DUMP(HDMI_INT_EN);
+	HDMI_DBG_DUMP(HDMI_INT_STA);
+	HDMI_DBG_DUMP(HDMI_INT_CLR);
+	HDMI_DBG_DUMP(HDMI_STA);
+	hdmi_dbg_sta(m, readl(hdmi->regs + HDMI_STA));
+	HDMI_DBG_DUMP(HDMI_ACTIVE_VID_XMIN);
+	hdmi_dbg_xmin(m, readl(hdmi->regs + HDMI_ACTIVE_VID_XMIN));
+	HDMI_DBG_DUMP(HDMI_ACTIVE_VID_XMAX);
+	hdmi_dbg_xmax(m, readl(hdmi->regs + HDMI_ACTIVE_VID_XMAX));
+	HDMI_DBG_DUMP(HDMI_ACTIVE_VID_YMIN);
+	hdmi_dbg_ymin(m, readl(hdmi->regs + HDMI_ACTIVE_VID_YMIN));
+	HDMI_DBG_DUMP(HDMI_ACTIVE_VID_YMAX);
+	hdmi_dbg_ymax(m, readl(hdmi->regs + HDMI_ACTIVE_VID_YMAX));
+	HDMI_DBG_DUMP(HDMI_DFLT_CHL0_DAT);
+	HDMI_DBG_DUMP(HDMI_DFLT_CHL1_DAT);
+	HDMI_DBG_DUMP(HDMI_DFLT_CHL2_DAT);
+	HDMI_DBG_DUMP(HDMI_SW_DI_CFG);
+	hdmi_dbg_sw_di_cfg(m, readl(hdmi->regs + HDMI_SW_DI_CFG));
+	if (hdmi->tx3g0c55phy)
+		sti_hdmi_tx3g0c55phy_show(hdmi, m);
+	else
+		sti_hdmi_tx3g4c28phy_show(hdmi, m);
+	seq_puts(m, "\n");
+
+	seq_printf(m, "\nAVI Infoframe (Data Island slot N=%d):",
+		   HDMI_IFRAME_SLOT_AVI);
+	HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_HEAD_WORD, HDMI_IFRAME_SLOT_AVI);
+	HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD0, HDMI_IFRAME_SLOT_AVI);
+	HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD1, HDMI_IFRAME_SLOT_AVI);
+	HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD2, HDMI_IFRAME_SLOT_AVI);
+	HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD3, HDMI_IFRAME_SLOT_AVI);
+	HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD4, HDMI_IFRAME_SLOT_AVI);
+	HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD5, HDMI_IFRAME_SLOT_AVI);
+	HDMI_DBG_DUMP_DI(HDMI_SW_DI_N_PKT_WORD6, HDMI_IFRAME_SLOT_AVI);
+	seq_puts(m, "\n");
+	if (drm_hdmi_avi_infoframe_from_display_mode(&info, mode) == 0) {
+		static const char *const colorspace[] = {
+			"RGB", "YUV422", "YUV444" };
+		static const char *const scan_mode[] = {
+			"none", "overscan", "underscan" };
+		static const char *const colorimetry[] = {
+			"none", "ITU 601", "ITU 709", "extended" };
+		static const char *const ext_colorimetry[] = {
+			"xvYCC 601", "xvYCC 709", "S YCC 601", "Adobe YCC 601",
+			"Adobe RGB" };
+		static const char *const pict_aspect[] = {
+			"none", "4:3", "16:9" };
+		static const char *const active_aspect[] = {
+			"", "", "16:9 top", "14:9 top", "16:9 center", "", "",
+			"", "picture", "4:3", "16:9", "14:9", "", "4:3 SP 14:9",
+			"16:9 SP 14:9", "16:9 SP 4:3" };
+		static const char *const quant_range[] = {
+			"default", "4:3", "16:9" };
+		static const char *const nups[] = {
+			"unknown", "horizontal", "vertical", "both" };
+		static const char *const ycc_quant_range[] = {
+			"limited", "full" };
+		static const char *const content_type[] = {
+			"none", "photo", "cinema", "game" };
+
+		snprintf(str, MAX_STRING_LENGTH, "\tversion:");
+		seq_printf(m, "%-25s %d\n", str, info.version);
+		snprintf(str, MAX_STRING_LENGTH, "\tlength:");
+		seq_printf(m, "%-25s %d\n", str, info.length);
+		snprintf(str, MAX_STRING_LENGTH, "\tcolorspace:");
+		seq_printf(m, "%-25s %s\n", str, colorspace[info.colorspace]);
+		snprintf(str, MAX_STRING_LENGTH, "\tscan mode:");
+		seq_printf(m, "%-25s %s\n", str, scan_mode[info.scan_mode]);
+		snprintf(str, MAX_STRING_LENGTH, "\tcolorimetry:");
+		seq_printf(m, "%-25s %s\n", str, colorimetry[info.colorimetry]);
+		if (info.colorimetry == HDMI_COLORIMETRY_EXTENDED) {
+			snprintf(str, MAX_STRING_LENGTH,
+				 " extended colorimetry:");
+			seq_printf(m, "%-25s %s\n", str,
+				   ext_colorimetry[info.extended_colorimetry]);
+		}
+		snprintf(str, MAX_STRING_LENGTH, "\tpicture aspect:");
+		seq_printf(m, "%-25s %s\n", str,
+			   pict_aspect[info.picture_aspect]);
+		snprintf(str, MAX_STRING_LENGTH, "\tactive aspect:");
+		seq_printf(m, "%-25s %s\n", str,
+			   active_aspect[info.active_aspect]);
+		snprintf(str, MAX_STRING_LENGTH, "\tquantization range:");
+		seq_printf(m, "%-25s %s\n", str,
+			   quant_range[info.quantization_range]);
+		snprintf(str, MAX_STRING_LENGTH, "\tycc quantization range:");
+		seq_printf(m, "%-25s %s\n", str,
+			   ycc_quant_range[info.ycc_quantization_range]);
+		snprintf(str, MAX_STRING_LENGTH, "\tnups:");
+		seq_printf(m, "%-25s %s\n", str, nups[info.nups]);
+		snprintf(str, MAX_STRING_LENGTH, "\tpixel repeat:");
+		seq_printf(m, "%-25s %d\n", str, info.pixel_repeat);
+		snprintf(str, MAX_STRING_LENGTH, "\tactive info valid:");
+		seq_printf(m, "%-25s %s\n", str,
+			   info.pixel_repeat ? "true" : "false");
+		snprintf(str, MAX_STRING_LENGTH, "\titc:");
+		seq_printf(m, "%-25s %s\n", str, info.itc ? "true" : "false");
+		snprintf(str, MAX_STRING_LENGTH, "\ttop bar:");
+		seq_printf(m, "%-25s %d\n", str, info.top_bar);
+		snprintf(str, MAX_STRING_LENGTH, "\tbottom bar:");
+		seq_printf(m, "%-25s %d\n", str, info.bottom_bar);
+		snprintf(str, MAX_STRING_LENGTH, "\tleft bar:");
+		seq_printf(m, "%-25s %d\n", str, info.left_bar);
+		snprintf(str, MAX_STRING_LENGTH, "\tright bar:");
+		seq_printf(m, "%-25s %d\n", str, info.right_bar);
+		snprintf(str, MAX_STRING_LENGTH, "\tcontent type:");
+		seq_printf(m, "%-25s %s\n", str,
+			   content_type[info.content_type]);
+		snprintf(str, MAX_STRING_LENGTH, "\tCEA video code:");
+		seq_printf(m, "%-25s %d\n", str, info.video_code);
+	}
+
+	seq_printf(m, "\nHDMI mode: %dx%d%s @%d",
+		   hdmi->mode.hdisplay,
+		   hdmi->mode.vdisplay,
+		   (hdmi->mode.flags & DRM_MODE_FLAG_INTERLACE) ?
+		   "i" : "p", hdmi->mode.vrefresh);
+}
+
+/*
+ * create the hdmi output
+ *
+ * @tvout: pointer on the tvout information
+ *
+ * Return pointer on the created tvout connector or NULL if error occurs
+ */
+struct sti_tvout_connector *sti_hdmi_create(struct sti_tvout *tvout)
+{
+	struct sti_hdmi *hdmi = container_of(hdmi_dev, struct sti_hdmi, dev);
+	struct device *dev = &hdmi->dev;
+	struct sti_tvout_connector *connector;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	if (!hdmi) {
+		DRM_INFO("%s: No hdmi device probed\n", __func__);
+		return NULL;
+	}
+
+	connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL);
+	if (!connector) {
+		DRM_ERROR("Failed to allocate memory for connector\n");
+		goto connector_alloc_failed;
+	}
+
+	/* Set the drm device handle */
+	hdmi->drm_dev = tvout->drm_dev;
+
+	/* DDC i2c driver */
+	if (i2c_add_driver(&ddc_driver)) {
+		DRM_ERROR("Failed to register ddc i2c driver\n");
+		goto i2c_failed;
+	}
+
+	/* Enable default interrupts */
+	writel(HDMI_DEFAULT_INT, hdmi->regs + HDMI_INT_EN);
+
+	connector->priv = (void *)hdmi;
+	connector->start = sti_hdmi_start;
+	connector->stop = sti_hdmi_stop;
+	connector->get_modes = sti_hdmi_get_modes;
+	connector->check_mode = sti_hdmi_check_mode;
+	connector->set_mode = sti_hdmi_set_mode;
+	connector->detect = sti_hdmi_detect;
+	connector->is_enabled = sti_hdmi_is_enabled;
+	connector->prepare = sti_hdmi_prepare;
+	connector->dbg_show = sti_hdmi_dbg_show;
+
+	return connector;
+
+i2c_failed:
+	devm_kfree(dev, connector);
+connector_alloc_failed:
+	return NULL;
+}
+
 static int sti_hdmi_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
diff --git a/drivers/gpu/drm/sti/sti_hdmi.h b/drivers/gpu/drm/sti/sti_hdmi.h
index c14c683..ed90a2c 100644
--- a/drivers/gpu/drm/sti/sti_hdmi.h
+++ b/drivers/gpu/drm/sti/sti_hdmi.h
@@ -11,6 +11,8 @@
 
 #include <drm/drmP.h>
 
+#include "sti_tvout.h"
+
 /* HDMI v2.9 macro cell */
 #define HDMI_CFG                        0x0000
 #define HDMI_INT_EN                     0x0004
@@ -181,6 +183,7 @@ struct hdmi_phy_config {
 	u32 config[4];
 };
 
+struct sti_tvout_connector *sti_hdmi_create(struct sti_tvout *tvout);
 void sti_hdmi_attach_ddc_client(struct i2c_client *ddc);
 
 int sti_hdmi_tx3g0c55phy_start(struct sti_hdmi *hdmi);
diff --git a/drivers/gpu/drm/sti/sti_tvout.c b/drivers/gpu/drm/sti/sti_tvout.c
new file mode 100644
index 0000000..df9a2c3
--- /dev/null
+++ b/drivers/gpu/drm/sti/sti_tvout.c
@@ -0,0 +1,682 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2013
+ * Author: Benjamin Gaignard <benjamin.gaignard at st.com> for STMicroelectronics.
+ * Author: Vincent Abriou <vincent.abriou at st.com> for STMicroelectronics
+ * License terms:  GNU General Public License (GPL), version 2
+ */
+
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "sti_tvout.h"
+#include "sti_hdmi.h"
+#include "sti_hda.h"
+
+/* glue regsiters */
+#define TVO_CSC_MAIN_M0                  0x000
+#define TVO_CSC_MAIN_M1                  0x004
+#define TVO_CSC_MAIN_M2                  0x008
+#define TVO_CSC_MAIN_M3                  0x00c
+#define TVO_CSC_MAIN_M4                  0x010
+#define TVO_CSC_MAIN_M5                  0x014
+#define TVO_CSC_MAIN_M6                  0x018
+#define TVO_CSC_MAIN_M7                  0x01c
+#define TVO_MAIN_IN_VID_FORMAT           0x030
+#define TVO_CSC_AUX_M0                   0x100
+#define TVO_CSC_AUX_M1                   0x104
+#define TVO_CSC_AUX_M2                   0x108
+#define TVO_CSC_AUX_M3                   0x10c
+#define TVO_CSC_AUX_M4                   0x110
+#define TVO_CSC_AUX_M5                   0x114
+#define TVO_CSC_AUX_M6                   0x118
+#define TVO_CSC_AUX_M7                   0x11c
+#define TVO_AUX_IN_VID_FORMAT            0x130
+#define TVO_VIP_HDF                      0x400
+#define TVO_HD_SYNC_SEL                  0x418
+#define TVO_HD_DAC_CFG_OFF               0x420
+#define TVO_VIP_HDMI                     0x500
+#define TVO_HDMI_FORCE_COLOR_0           0x504
+#define TVO_HDMI_FORCE_COLOR_1           0x508
+#define TVO_HDMI_CLIP_VALUE_B_CB         0x50c
+#define TVO_HDMI_CLIP_VALUE_Y_G          0x510
+#define TVO_HDMI_CLIP_VALUE_R_CR         0x514
+#define TVO_HDMI_SYNC_SEL                0x518
+#define TVO_HDMI_DFV_OBS                 0x540
+
+#define TVO_IN_FMT_SIGNED                (1 << 0)
+#define TVO_SYNC_EXT                     (1 << 4)
+
+#define TVO_VIP_REORDER_R_SHIFT          24
+#define TVO_VIP_REORDER_G_SHIFT          20
+#define TVO_VIP_REORDER_B_SHIFT          16
+#define TVO_VIP_REORDER_MASK             0x3
+#define TVO_VIP_REORDER_Y_G_SEL          0
+#define TVO_VIP_REORDER_CB_B_SEL         1
+#define TVO_VIP_REORDER_CR_R_SEL         2
+
+#define TVO_VIP_CLIP_SHIFT               8
+#define TVO_VIP_CLIP_MASK                0x7
+#define TVO_VIP_CLIP_DISABLED            0
+#define TVO_VIP_CLIP_EAV_SAV             1
+#define TVO_VIP_CLIP_LIMITED_RANGE_RGB_Y 2
+#define TVO_VIP_CLIP_LIMITED_RANGE_CB_CR 3
+#define TVO_VIP_CLIP_PROG_RANGE          4
+
+#define TVO_VIP_RND_SHIFT                4
+#define TVO_VIP_RND_MASK                 0x3
+#define TVO_VIP_RND_8BIT_ROUNDED         0
+#define TVO_VIP_RND_10BIT_ROUNDED        1
+#define TVO_VIP_RND_12BIT_ROUNDED        2
+
+#define TVO_VIP_SEL_INPUT_MASK           0xf
+#define TVO_VIP_SEL_INPUT_MAIN           0x0
+#define TVO_VIP_SEL_INPUT_AUX            0x8
+#define TVO_VIP_SEL_INPUT_FORCE_COLOR    0xf
+#define TVO_VIP_SEL_INPUT_BYPASS_MASK    0x1
+#define TVO_VIP_SEL_INPUT_BYPASSED       1
+
+#define TVO_SYNC_MAIN_VTG_SET_REF        0x00
+#define TVO_SYNC_MAIN_VTG_SET_1          0x01
+#define TVO_SYNC_MAIN_VTG_SET_2          0x02
+#define TVO_SYNC_MAIN_VTG_SET_3          0x03
+#define TVO_SYNC_MAIN_VTG_SET_4          0x04
+#define TVO_SYNC_MAIN_VTG_SET_5          0x05
+#define TVO_SYNC_MAIN_VTG_SET_6          0x06
+#define TVO_SYNC_AUX_VTG_SET_REF         0x10
+#define TVO_SYNC_AUX_VTG_SET_1           0x11
+#define TVO_SYNC_AUX_VTG_SET_2           0x12
+#define TVO_SYNC_AUX_VTG_SET_3           0x13
+#define TVO_SYNC_AUX_VTG_SET_4           0x14
+#define TVO_SYNC_AUX_VTG_SET_5           0x15
+#define TVO_SYNC_AUX_VTG_SET_6           0x16
+
+#define TVO_SYNC_HD_DCS_SHIFT            8
+
+/* Preformatter conversion matrix */
+static const u32 rgb_to_ycbcr_601[8] = {
+	0xF927082E, 0x04C9FEAB, 0x01D30964, 0xFA95FD3D,
+	0x0000082E, 0x00002000, 0x00002000, 0x00000000
+};
+
+/* 709 RGB to YCbCr */
+static const u32 rgb_to_ycbcr_709[8] = {
+	0xF891082F, 0x0367FF40, 0x01280B71, 0xF9B1FE20,
+	0x0000082F, 0x00002000, 0x00002000, 0x00000000
+};
+
+/*
+ * Helper to write bit field
+ *
+ * @addr: register to update
+ * @val: value to write
+ * @mask: bit field mask to use
+ */
+static inline void tvout_reg_writemask(void __iomem *addr, u32 val, u32 mask)
+{
+	u32 old = readl(addr);
+
+	val = (val & mask) | (old & ~mask);
+	writel(val, addr);
+}
+
+/*
+ * Set the Channel order of a VIP
+ *
+ * @vip_reg: VIP regsiter
+ * @cr_r
+ * @y_g
+ * @cb_c : values for each output
+ */
+static void tvout_vip_set_color_order(void __iomem *vip_reg,
+				      u32 cr_r, u32 y_g, u32 cb_b)
+{
+	u32 val, mask;
+
+	mask = TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_R_SHIFT;
+	mask |= TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_G_SHIFT;
+	mask |= TVO_VIP_REORDER_MASK << TVO_VIP_REORDER_B_SHIFT;
+	val = cr_r << TVO_VIP_REORDER_R_SHIFT;
+	val |= y_g << TVO_VIP_REORDER_G_SHIFT;
+	val |= cb_b << TVO_VIP_REORDER_B_SHIFT;
+	tvout_reg_writemask(vip_reg, val, mask);
+}
+
+/*
+ * Set the clipping mode of a VIP
+ *
+ * @vip_reg: VIP regsiter
+ * @range  : clipping range
+ */
+static void tvout_vip_set_clip_mode(void __iomem *vip_reg, u32 range)
+{
+	tvout_reg_writemask(vip_reg,
+			    range << TVO_VIP_CLIP_SHIFT,
+			    TVO_VIP_CLIP_MASK << TVO_VIP_CLIP_SHIFT);
+}
+
+/*
+ * Set the rounded value of a VIP
+ *
+ * @vip_reg: VIP regsiter
+ * @rnd: rounded val per component
+ */
+static void tvout_vip_set_rnd(void __iomem *vip_reg, u32 rnd)
+{
+	tvout_reg_writemask(vip_reg,
+			    rnd << TVO_VIP_RND_SHIFT,
+			    TVO_VIP_RND_MASK << TVO_VIP_RND_SHIFT);
+}
+
+/*
+ * Select the VIP input
+ *
+ * @vip_reg: VIP regsiter
+ * @sel_input: selected_input (main/aux + conv)
+ */
+static void tvout_vip_set_sel_input(void __iomem *vip_reg,
+				    bool main_path,
+				    bool sel_input_logic_inverted,
+				    enum sti_tvout_video_out_type video_out)
+{
+	u32 sel_input;
+
+	if (main_path)
+		sel_input = TVO_VIP_SEL_INPUT_MAIN;
+	else
+		sel_input = TVO_VIP_SEL_INPUT_AUX;
+
+	switch (video_out) {
+	case STI_TVOUT_VIDEO_OUT_RGB:
+		sel_input |= TVO_VIP_SEL_INPUT_BYPASSED;
+		break;
+	case STI_TVOUT_VIDEO_OUT_YUV:
+		sel_input &= ~TVO_VIP_SEL_INPUT_BYPASSED;
+		break;
+	}
+
+	/* On stih407 chip the sel_input bypass mode logic is inverted */
+	if (sel_input_logic_inverted)
+		sel_input = sel_input ^ TVO_VIP_SEL_INPUT_BYPASS_MASK;
+
+	tvout_reg_writemask(vip_reg, sel_input, TVO_VIP_SEL_INPUT_MASK);
+}
+
+/*
+ * Select the input video signed or unsigned
+ *
+ * @vip_reg: VIP regsiter
+ * @in_vid_signed: used video input format
+ */
+static void tvout_vip_set_in_vid_fmt(void __iomem *vip_reg, u32 in_vid_fmt)
+{
+	tvout_reg_writemask(vip_reg, in_vid_fmt, TVO_IN_FMT_SIGNED);
+}
+
+/*
+ * Start VIP block for HDMI output
+ *
+ * @tvout: pointer on tvout structure
+ * @main_path: true if main path has to be used in the vip configuration
+ *	  else aux path is used.
+ */
+static void tvout_hdmi_start(struct sti_tvout *tvout, bool main_path)
+{
+	struct device_node *node = tvout->dev->of_node;
+	bool sel_input_logic_inverted = false;
+
+	dev_dbg(tvout->dev, "%s\n", __func__);
+
+	if (main_path) {
+		DRM_DEBUG_DRIVER("main vip for hdmi\n");
+		/* Select the input sync for hdmi = VTG set 1 */
+		writel(TVO_SYNC_MAIN_VTG_SET_1,
+		       tvout->regs + TVO_HDMI_SYNC_SEL);
+	} else {
+		DRM_DEBUG_DRIVER("aux vip for hdmi\n");
+		/* Select the input sync for hdmi = VTG set 1 */
+		writel(TVO_SYNC_AUX_VTG_SET_1, tvout->regs + TVO_HDMI_SYNC_SEL);
+	}
+
+	/* Set color channel order */
+	tvout_vip_set_color_order(tvout->regs + TVO_VIP_HDMI,
+				  TVO_VIP_REORDER_CR_R_SEL,
+				  TVO_VIP_REORDER_Y_G_SEL,
+				  TVO_VIP_REORDER_CB_B_SEL);
+
+	/* Set clipping mode (Limited range RGB/Y) */
+	tvout_vip_set_clip_mode(tvout->regs + TVO_VIP_HDMI,
+				TVO_VIP_CLIP_LIMITED_RANGE_RGB_Y);
+
+	/* Set round mode (rounded to 8-bit per component) */
+	tvout_vip_set_rnd(tvout->regs + TVO_VIP_HDMI, TVO_VIP_RND_8BIT_ROUNDED);
+
+	if (of_device_is_compatible(node, "st,stih407-tvout")) {
+		/* Set input video format */
+		tvout_vip_set_in_vid_fmt(tvout->regs + TVO_MAIN_IN_VID_FORMAT,
+					 TVO_IN_FMT_SIGNED);
+		sel_input_logic_inverted = true;
+	}
+
+	/* Input selection */
+	tvout_vip_set_sel_input(tvout->regs + TVO_VIP_HDMI,
+				main_path,
+				sel_input_logic_inverted,
+				STI_TVOUT_VIDEO_OUT_RGB);
+}
+
+/*
+ * Prepare/configure VIP block for HDMI output
+ *
+ * @tvout: pointer on tvout structure
+ */
+static void tvout_hdmi_prepare(struct sti_tvout *tvout)
+{
+	dev_dbg(tvout->dev, "%s\n", __func__);
+
+	/* Reset VIP register */
+	writel(0x00000000, tvout->regs + TVO_VIP_HDMI);
+}
+
+/*
+ * Disable HDMI VIP
+ *
+ * @tvout: pointer on tvout structure
+ */
+static void tvout_hdmi_stop(struct sti_tvout *tvout)
+{
+	dev_dbg(tvout->dev, "%s\n", __func__);
+
+	/* Reset VIP register */
+	writel(0x00000000, tvout->regs + TVO_VIP_HDMI);
+}
+
+/*
+ * Start HDF VIP and HD DAC
+ *
+ * @tvout: pointer on tvout structure
+ * @main_path: true if main path has to be used in the vip configuration
+ *	  else aux path is used.
+ */
+static void tvout_hda_start(struct sti_tvout *tvout, bool main_path)
+{
+	struct device_node *node = tvout->dev->of_node;
+	bool sel_input_logic_inverted = false;
+
+	dev_dbg(tvout->dev, "%s\n", __func__);
+
+	if (!main_path) {
+		DRM_ERROR("HD Analog on aux not implemented\n");
+		return;
+	}
+
+	DRM_DEBUG_DRIVER("main vip for HDF\n");
+
+	/* Set color channel order */
+	tvout_vip_set_color_order(tvout->regs + TVO_VIP_HDF,
+				  TVO_VIP_REORDER_CR_R_SEL,
+				  TVO_VIP_REORDER_Y_G_SEL,
+				  TVO_VIP_REORDER_CB_B_SEL);
+
+	/* Set clipping mode (Limited range RGB/Y) */
+	tvout_vip_set_clip_mode(tvout->regs + TVO_VIP_HDF,
+				TVO_VIP_CLIP_LIMITED_RANGE_CB_CR);
+
+	/* Set round mode (rounded to 10-bit per component) */
+	tvout_vip_set_rnd(tvout->regs + TVO_VIP_HDF, TVO_VIP_RND_10BIT_ROUNDED);
+
+	if (of_device_is_compatible(node, "st,stih407-tvout")) {
+		/* Set input video format */
+		tvout_vip_set_in_vid_fmt(tvout->regs + TVO_MAIN_IN_VID_FORMAT,
+					 TVO_IN_FMT_SIGNED);
+		sel_input_logic_inverted = true;
+	}
+
+	/* Input selection */
+	tvout_vip_set_sel_input(tvout->regs + TVO_VIP_HDF,
+				main_path,
+				sel_input_logic_inverted,
+				STI_TVOUT_VIDEO_OUT_YUV);
+
+	/* Select the input sync for HD analog = VTG set 3
+	 * and HD DCS = VTG set 2 */
+	writel((TVO_SYNC_MAIN_VTG_SET_2 << TVO_SYNC_HD_DCS_SHIFT) |
+	       TVO_SYNC_MAIN_VTG_SET_3, tvout->regs + TVO_HD_SYNC_SEL);
+
+	/* Power up HD DAC */
+	writel(0, tvout->regs + TVO_HD_DAC_CFG_OFF);
+}
+
+/*
+ * Prepare/configure HDF VIP and HD DAC
+ *
+ * @tvout: pointer on tvout structure
+ */
+static void tvout_hda_prepare(struct sti_tvout *tvout)
+{
+	dev_dbg(tvout->dev, "%s\n", __func__);
+
+	/* Reset VIP register */
+	writel(0x00000000, tvout->regs + TVO_VIP_HDF);
+
+	/* Power down HD DAC */
+	writel(1, tvout->regs + TVO_HD_DAC_CFG_OFF);
+}
+
+/*
+ * Stop HDF VIP and HD DAC
+ *
+ * @tvout: pointer on tvout structure
+ */
+static void tvout_hda_stop(struct sti_tvout *tvout)
+{
+	dev_dbg(tvout->dev, "%s\n", __func__);
+
+	/* Reset VIP register */
+	writel(0x00000000, tvout->regs + TVO_VIP_HDF);
+
+	/* Power down HD DAC */
+	writel(1, tvout->regs + TVO_HD_DAC_CFG_OFF);
+}
+
+/*
+ * Check if the connector is connected
+ *
+ * @tvout: pointer on tvout structure
+ * @type: type of connector
+ *
+ * Return true if connected
+ */
+bool sti_tvout_connector_detect(struct sti_tvout *tvout,
+				enum sti_tvout_connector_type type)
+{
+	struct sti_tvout_connector *connector = tvout->connector[type];
+
+	dev_dbg(tvout->dev, "%s\n", __func__);
+
+	if (connector)
+		if (connector->detect)
+			return connector->detect(connector);
+
+	return false;
+}
+
+/*
+ * Forward drm display mode information to the connector
+ *
+ * @tvout: pointer on tvout structure
+ * @mode: selected display mode
+ * @type: type of connector
+ *
+ * Return -1 if error occurs
+ */
+int sti_tvout_set_mode(struct sti_tvout *tvout, struct drm_display_mode *mode,
+		       enum sti_tvout_connector_type type)
+{
+	struct sti_tvout_connector *connector = tvout->connector[type];
+
+	dev_dbg(tvout->dev, "%s\n", __func__);
+
+	if (connector)
+		if (connector->set_mode)
+			return connector->set_mode(connector, mode);
+
+	return -1;
+}
+
+/*
+ * Get modes
+ *
+ * @tvout: pointer on tvout structure
+ * @type: type of connector
+ * @drm_connector: pointer on the connector
+ *
+ * Return Nb of modes, -1 if error
+ */
+int sti_tvout_get_modes(struct sti_tvout *tvout,
+			enum sti_tvout_connector_type type,
+			struct drm_connector *drm_connector)
+{
+	struct sti_tvout_connector *connector = tvout->connector[type];
+
+	dev_dbg(tvout->dev, "%s\n", __func__);
+
+	if (connector)
+		if (connector->get_modes)
+			return connector->get_modes(drm_connector);
+
+	return -1;
+}
+
+/*
+ * Check if the mode is supported
+ *
+ * function used to filter unsupported mode
+ *
+ * @tvout: pointer on tvout structure
+ * @type: type of connector
+ * @mode: drm display mode
+ *
+ * Return -1 if error occurs
+ */
+int sti_tvout_check_mode(struct sti_tvout *tvout,
+			 enum sti_tvout_connector_type type,
+			 struct drm_display_mode *mode)
+{
+	struct sti_tvout_connector *connector = tvout->connector[type];
+
+	dev_dbg(tvout->dev, "%s\n", __func__);
+
+	if (connector)
+		if (connector->check_mode)
+			return connector->check_mode(connector, mode);
+
+	return -1;
+}
+
+/*
+ * Prepare / initialize depending on the connector type
+ *
+ * @tvout: pointer on tvout structure
+ * @type: type of connector
+ *
+ * Return -1 if error occurs
+ */
+int sti_tvout_prepare(struct sti_tvout *tvout,
+		      enum sti_tvout_connector_type type)
+{
+	struct sti_tvout_connector *connector = tvout->connector[type];
+	int ret = -1;
+
+	dev_dbg(tvout->dev, "%s\n", __func__);
+
+	if (connector)
+		if (connector->prepare)
+			connector->prepare(connector);
+
+	switch (type) {
+	case STI_TVOUT_CONNECTOR_HDMI:
+		tvout_hdmi_prepare(tvout);
+		ret = 0;
+		break;
+	case STI_TVOUT_CONNECTOR_HDA:
+		tvout_hda_prepare(tvout);
+		ret = 0;
+		break;
+	case STI_TVOUT_CONNECTOR_DVO:
+	case STI_TVOUT_CONNECTOR_DENC:
+	default:
+		/* Not yet supported */
+		ret = -1;
+		break;
+	}
+
+	return ret;
+}
+
+/*
+ * Commit / start depending on the connector type
+ *
+ * @tvout: pointer on tvout structure
+ * @type: type of connector
+ * @main_path: true if main path need to be use (false for aux path)
+ *
+ * Return -1 if error occurs
+ */
+int sti_tvout_commit(struct sti_tvout *tvout,
+		     enum sti_tvout_connector_type type, bool main_path)
+{
+	struct sti_tvout_connector *connector = tvout->connector[type];
+	int ret = 0;
+	u32 matrix_offset;
+	int i;
+
+	dev_dbg(tvout->dev, "%s\n", __func__);
+
+	if (!connector)
+		return -1;
+
+	connector->main_path = main_path;
+
+	if (connector->start) {
+		ret = connector->start(connector);
+		if (ret) {
+			DRM_ERROR("Unable to properly start connector\n");
+			return -1;
+		}
+	}
+
+	/* Set preformatter matrix */
+	matrix_offset = main_path ? TVO_CSC_MAIN_M0 : TVO_CSC_AUX_M0;
+	for (i = 0; i < 8; i++)
+		writel(rgb_to_ycbcr_601[i],
+		       tvout->regs + matrix_offset + (i * 4));
+
+	switch (type) {
+	case STI_TVOUT_CONNECTOR_HDMI:
+		tvout_hdmi_start(tvout, main_path);
+		return 0;
+	case STI_TVOUT_CONNECTOR_HDA:
+		tvout_hda_start(tvout, main_path);
+		return 0;
+	case STI_TVOUT_CONNECTOR_DVO:
+	case STI_TVOUT_CONNECTOR_DENC:
+	default:
+		/* Not yet supported */
+		return -1;
+	}
+}
+
+/*
+ * Disable / stop the tvout depending on the connector type
+ *
+ * @tvout: pointer on tvout structure
+ * @type: type of connector
+ */
+void sti_tvout_disable(struct sti_tvout *tvout,
+		       enum sti_tvout_connector_type type)
+{
+	struct sti_tvout_connector *connector = tvout->connector[type];
+
+	dev_dbg(tvout->dev, "%s\n", __func__);
+
+	switch (type) {
+	case STI_TVOUT_CONNECTOR_HDMI:
+		tvout_hdmi_stop(tvout);
+		break;
+	case STI_TVOUT_CONNECTOR_HDA:
+		tvout_hda_stop(tvout);
+		break;
+	case STI_TVOUT_CONNECTOR_DVO:
+	case STI_TVOUT_CONNECTOR_DENC:
+	default:
+		/* Not yet supported */
+		return;
+	}
+
+	if (connector)
+		if (connector->stop)
+			connector->stop(connector);
+}
+
+static int sti_tvout_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *node = dev->of_node;
+	struct sti_tvout *tvout;
+	struct resource *res;
+	int ret;
+
+	dev_dbg(dev, "%s\n", __func__);
+
+	if (!node) {
+		DRM_ERROR("no device node\n");
+		return -ENODEV;
+	}
+
+	tvout = devm_kzalloc(dev, sizeof(*tvout), GFP_KERNEL);
+	if (!tvout) {
+		DRM_ERROR("failed to allocate compositor context\n");
+		return -ENOMEM;
+	}
+	DRM_DEBUG_DRIVER("tvout %p\n", tvout);
+	tvout->dev = dev;
+
+	/* Get Memory ressources */
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tvout-reg");
+	if (!res) {
+		DRM_ERROR("Invalid glue resource\n");
+		return -ENOMEM;
+	}
+	tvout->regs = devm_ioremap_nocache(dev, res->start, resource_size(res));
+	if (IS_ERR(tvout->regs))
+		return PTR_ERR(tvout->regs);
+
+	/* Get reset resources */
+	tvout->reset = devm_reset_control_get(dev, "tvout");
+	/* Take tvout out of reset */
+	if (!IS_ERR(tvout->reset))
+		reset_control_deassert(tvout->reset);
+
+	ret = of_platform_populate(node, NULL, NULL, dev);
+	if (ret) {
+		DRM_ERROR("failed to add hdmi core\n");
+		return ret;
+	}
+
+	/* List supported tvout connector */
+	tvout->connector_create[STI_TVOUT_CONNECTOR_HDMI] = sti_hdmi_create;
+	tvout->connector_create[STI_TVOUT_CONNECTOR_HDA] = sti_hda_create;
+	tvout->connector_create[STI_TVOUT_CONNECTOR_DVO] = NULL;
+	tvout->connector_create[STI_TVOUT_CONNECTOR_DENC] = NULL;
+
+	platform_set_drvdata(pdev, tvout);
+
+	return 0;
+}
+
+static struct of_device_id tvout_match_types[] = {
+	{
+	 .compatible = "st,stih416-tvout",
+	 },
+	{
+	 .compatible = "st,stih407-tvout",
+	 },
+	{ /* end node */ }
+};
+
+struct platform_driver sti_tvout_driver = {
+	.driver = {
+		   .name = "sti-tvout",
+		   .owner = THIS_MODULE,
+		   .of_match_table = tvout_match_types,
+		   },
+	.probe = sti_tvout_probe,
+};
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sti/sti_tvout.h b/drivers/gpu/drm/sti/sti_tvout.h
new file mode 100644
index 0000000..f61a49c
--- /dev/null
+++ b/drivers/gpu/drm/sti/sti_tvout.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2013
+ * Author: Vincent Abriou <vincent.abriou at st.com> for STMicroelectronics.
+ * License terms:  GNU General Public License (GPL), version 2
+ */
+
+#ifndef _STI_TVOUT_H_
+#define _STI_TVOUT_H_
+
+#include <linux/clk.h>
+
+/*
+ * STI TVout connector structure
+ *
+ * @priv: private structure associated to the connector type
+ * @start: start the connector
+ * @stop: stop the connector
+ * @get_modes: get modes potentially supported
+ * @check_mode: check if a mode is really supported
+ * @set_mode: set the drm display mode in the specific connector structure
+ * @detect: detect if connector is connected
+ * @prepare: prepare the connector
+ * @is_enabled: is the connector enabled
+ * @dbg_show: dump debug information
+ */
+struct sti_tvout_connector {
+	void *priv;
+	bool main_path;
+	int (*start)(struct sti_tvout_connector *connector);
+	void (*stop)(struct sti_tvout_connector *connector);
+	int (*get_modes)(struct drm_connector *drm_connector);
+	int (*check_mode)(struct sti_tvout_connector *connector,
+			struct drm_display_mode *mode);
+	int (*set_mode)(struct sti_tvout_connector *connector,
+			struct drm_display_mode *mode);
+	bool (*detect)(struct sti_tvout_connector *connector);
+	void (*prepare)(struct sti_tvout_connector *connector);
+	bool (*is_enabled)(struct sti_tvout_connector *connector);
+	void (*dbg_show)(struct sti_tvout_connector *connector,
+			struct seq_file *m);
+};
+
+/*
+ * enum listing the supported connector
+ */
+enum sti_tvout_connector_type {
+	STI_TVOUT_CONNECTOR_HDMI,
+	STI_TVOUT_CONNECTOR_HDA,
+	STI_TVOUT_CONNECTOR_DVO,
+	STI_TVOUT_CONNECTOR_DENC,
+	STI_TVOUT_CONNECTOR_MAX,
+};
+
+/*
+ * enum listing the supported output data format
+ */
+enum sti_tvout_video_out_type {
+	STI_TVOUT_VIDEO_OUT_RGB,
+	STI_TVOUT_VIDEO_OUT_YUV,
+};
+
+/*
+ * STI TVout structure
+ *
+ * @dev: pointer to driver device
+ * @regs: registers
+ * @reset: reset control of the tvout
+ * @hw_id: HW revision of the IP
+ * @connector_create: list of function to register a connector
+ * @connector: list of registered connector
+ */
+struct sti_tvout {
+	struct device *dev;
+	struct drm_device *drm_dev;
+	void __iomem *regs;
+	struct reset_control *reset;
+	int hw_id;
+	struct sti_tvout_connector *(*connector_create[STI_TVOUT_CONNECTOR_MAX])
+		(struct sti_tvout *tvout);
+	struct sti_tvout_connector *connector[STI_TVOUT_CONNECTOR_MAX];
+};
+
+bool sti_tvout_connector_detect(struct sti_tvout *tvout,
+		enum sti_tvout_connector_type type);
+int  sti_tvout_set_mode(struct sti_tvout *tvout,
+		struct drm_display_mode *mode,
+		enum sti_tvout_connector_type type);
+int  sti_tvout_get_modes(struct sti_tvout *tvout,
+		enum sti_tvout_connector_type type,
+		struct drm_connector *drm_connector);
+int  sti_tvout_check_mode(struct sti_tvout *tvout,
+		enum sti_tvout_connector_type type,
+		struct drm_display_mode *mode);
+int  sti_tvout_prepare(struct sti_tvout *tvout,
+		enum sti_tvout_connector_type type);
+int  sti_tvout_commit(struct sti_tvout *tvout,
+		enum sti_tvout_connector_type type,
+		bool main_path);
+void  sti_tvout_disable(struct sti_tvout *tvout,
+		enum sti_tvout_connector_type type);
+
+int sti_tvout_hdmi_dbg_show(struct seq_file *m, void *arg);
+int sti_tvout_hda_dbg_show(struct seq_file *m, void *arg);
+
+#endif
-- 
1.9.0



More information about the dri-devel mailing list