Add driver for HDMI ouput

Signed-off-by: Benjamin Gaignard <benjamin.gaignard at linaro.org>
---
 drivers/gpu/drm/sti/Makefile               |   5 +
 drivers/gpu/drm/sti/sti_hdmi.c             | 529 +++++++++++++++++++++++++++++
 drivers/gpu/drm/sti/sti_hdmi.h             | 195 +++++++++++
 drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c | 398 ++++++++++++++++++++++
 drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c | 224 ++++++++++++
 5 files changed, 1351 insertions(+)
 create mode 100644 drivers/gpu/drm/sti/sti_hdmi.c
 create mode 100644 drivers/gpu/drm/sti/sti_hdmi.h
 create mode 100644 drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c
 create mode 100644 drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c

diff --git a/drivers/gpu/drm/sti/Makefile b/drivers/gpu/drm/sti/Makefile
index 79fdcb6..5295fc7 100644
--- a/drivers/gpu/drm/sti/Makefile
+++ b/drivers/gpu/drm/sti/Makefile
@@ -1,4 +1,9 @@
 ccflags-y := -Iinclude/drm

+stidrm-y := sti_hdmi.o \
+                       sti_hdmi_tx3g0c55phy.o \
+                       sti_hdmi_tx3g4c28phy.o
+
 obj-$(CONFIG_VTAC_STI) += sti_vtac_tx.o sti_vtac_rx.o
 obj-$(CONFIG_VTG_STI) += sti_vtg.o sti_vtg_utils.o
+obj-$(CONFIG_DRM_STI) += stidrm.o
\ No newline at end of file
diff --git a/drivers/gpu/drm/sti/sti_hdmi.c b/drivers/gpu/drm/sti/sti_hdmi.c
new file mode 100644
index 0000000..02b0524
--- /dev/null
+++ b/drivers/gpu/drm/sti/sti_hdmi.c
@@ -0,0 +1,529 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2013
+ * 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/component.h>
+#include <linux/hdmi.h>
+#include <linux/module.h>
+#include <linux/of_gpio.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+
+#include "sti_hdmi.h"
+#include "sti_vtg_utils.h"
+
+/* Reference to the hdmi device */
+struct device *hdmi_dev;
+
+/*
+ * Helper to write bit field
+ *
+ * @addr: register to update
+ * @val: value to write
+ * @mask: bit field mask to use
+ */
+static inline void hdmi_reg_writemask(void __iomem *addr, u32 val, u32 mask)
+{
+       u32 old = readl(addr);
+
+       val = (val & mask) | (old & ~mask);
+       writel(val, addr);
+}
+
+/*
+ * HDMI interrupt handler
+ *
+ * @irq: irq number
+ * @arg: connector structure
+ */
+static irqreturn_t hdmi_irq_thread(int irq, void *arg)
+{
+       struct sti_hdmi *hdmi = arg;
+       u32 status;
+
+       /* read interrupt status */
+       status = readl(hdmi->regs + HDMI_INT_STA);
+
+       /* PLL lock interrupt */
+       if (status & HDMI_INT_DLL_LCK) {
+               hdmi->event_received = true;
+               wake_up_interruptible(&hdmi->wait_event);
+       }
+
+       /* Hot plug detection */
+       if (status & HDMI_INT_HOT_PLUG) {
+               hdmi->hpd = gpio_get_value(hdmi->hpd_gpio);
+               if (hdmi->drm_dev)
+                       drm_helper_hpd_irq_event(hdmi->drm_dev);
+       }
+
+       /* Sw reset completed */
+       if (status & HDMI_INT_SW_RST) {
+               hdmi->event_received = true;
+               wake_up_interruptible(&hdmi->wait_event);
+       }
+
+       /* clear interrupt status */
+       writel(status, hdmi->regs + HDMI_INT_CLR);
+
+       /* TODO: check why this sync bus write solves the problem which
+        * is that without this line, the handler is sometimes called twice
+        */
+       /* sync bus write */
+       readl(hdmi->regs + HDMI_INT_STA);
+
+       return IRQ_HANDLED;
+}
+
+/*
+ * Start hdmi phy interface
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ *
+ * Return -1 if error occurs
+ */
+static int hdmi_phy_start(struct sti_hdmi *hdmi)
+{
+       DRM_DEBUG_DRIVER("\n");
+
+       if (hdmi->tx3g0c55phy)
+               return sti_hdmi_tx3g0c55phy_start(hdmi);
+
+       return sti_hdmi_tx3g4c28phy_start(hdmi);
+}
+
+/*
+ * Stop hdmi phy interface
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ */
+static void hdmi_phy_stop(struct sti_hdmi *hdmi)
+{
+       DRM_DEBUG_DRIVER("\n");
+
+       if (hdmi->tx3g0c55phy)
+               sti_hdmi_tx3g0c55phy_stop(hdmi);
+       else
+               sti_hdmi_tx3g4c28phy_stop(hdmi);
+}
+
+/*
+ * Set hdmi active area depending on the drm display mode selected
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ */
+static void hdmi_active_area(struct sti_hdmi *hdmi)
+{
+       u32 xmin, xmax;
+       u32 ymin, ymax;
+
+       DRM_DEBUG_DRIVER("\n");
+
+       /*
+        *       Active        Front        Sync       Back          Active
+        *       Region        Porch                  Porch          Region
+        * <---------------><-------->0<---------><--------><----------------->
+        *
+        *   ///////////////|                               |  ///////////////|
+        *  /////////////// |                               | /////////////// |
+        * ///////////////  |.........            ..........|///////////////  |
+        *                            0___________      x/ymin           x/ymax
+        *
+        * <--[hv]display-->                                 <--[hv]display-->
+        * <--[hv]sync_start--------->                       <--[hv]sync_start-
+        * <--[hv]sync_end----------------------->           <--[hv]sync_end---
+        * <--[hv]total------------------------------------> <--[hv]total------
+        */
+
+       xmin = sti_vtg_get_pixel_number(hdmi->mode, 0);
+       xmax = sti_vtg_get_pixel_number(hdmi->mode, hdmi->mode.hdisplay - 1);
+       ymin = sti_vtg_get_line_number(hdmi->mode, 0);
+       ymax = sti_vtg_get_line_number(hdmi->mode, hdmi->mode.vdisplay - 1);
+
+       writel(xmin, hdmi->regs + HDMI_ACTIVE_VID_XMIN);
+       writel(xmax, hdmi->regs + HDMI_ACTIVE_VID_XMAX);
+       writel(ymin, hdmi->regs + HDMI_ACTIVE_VID_YMIN);
+       writel(ymax, hdmi->regs + HDMI_ACTIVE_VID_YMAX);
+
+       DRM_DEBUG_DRIVER("xmin=%d xmax=%d ymin=%d ymax=%d\n",
+                        xmin, xmax, ymin, ymax);
+}
+
+/*
+ * Overall hdmi configuration
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ */
+static void hdmi_config(struct sti_hdmi *hdmi)
+{
+       u32 val;
+       u32 mask;
+
+       DRM_DEBUG_DRIVER("\n");
+
+       /* Clear overrun and underrun fifo */
+       mask = HDMI_CFG_FIFO_OVERRUN_CLR | HDMI_CFG_FIFO_UNDERRUN_CLR;
+       val = HDMI_CFG_FIFO_OVERRUN_CLR | HDMI_CFG_FIFO_UNDERRUN_CLR;
+
+       /* Enable HDMI mode not DVI */
+       mask |= HDMI_CFG_HDMI_NOT_DVI | HDMI_CFG_ESS_NOT_OESS;
+       val |= HDMI_CFG_HDMI_NOT_DVI | HDMI_CFG_ESS_NOT_OESS;
+
+       /* Enable sink term detection */
+       mask |= HDMI_CFG_SINK_TERM_DET_EN;
+       val |= HDMI_CFG_SINK_TERM_DET_EN;
+
+       /* Set Hsync polarity */
+       if ((hdmi->mode.flags && DRM_MODE_FLAG_NHSYNC)
+           == DRM_MODE_FLAG_NHSYNC) {
+               DRM_DEBUG_DRIVER("H Sync Negative\n");
+               mask |= HDMI_CFG_H_SYNC_POL_NEG;
+               val |= HDMI_CFG_H_SYNC_POL_NEG;
+       }
+
+       /* Set Vsync polarity */
+       if ((hdmi->mode.flags && DRM_MODE_FLAG_NVSYNC)
+           == DRM_MODE_FLAG_NVSYNC) {
+               DRM_DEBUG_DRIVER("V Sync Negative\n");
+               mask |= HDMI_CFG_V_SYNC_POL_NEG;
+               val |= HDMI_CFG_V_SYNC_POL_NEG;
+       }
+
+       /* Enable HDMI */
+       mask |= HDMI_CFG_DEVICE_EN;
+       val |= HDMI_CFG_DEVICE_EN;
+
+       hdmi_reg_writemask(hdmi->regs + HDMI_CFG, val, mask);
+}
+
+/*
+ * Prepare and configure the AVI infoframe
+ *
+ * AVI infoframe are transmitted at least once per two video field and
+ * contains information about HDMI transmission mode such as color space,
+ * colorimetry, ...
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ *
+ * Return negative value if error occurs
+ */
+static int hdmi_avi_infoframe_config(struct sti_hdmi *hdmi)
+{
+       struct drm_display_mode *mode = &hdmi->mode;
+       struct hdmi_avi_infoframe infoframe;
+       u8 buffer[HDMI_INFOFRAME_HEADER_SIZE + HDMI_AVI_INFOFRAME_SIZE];
+       u8 *frame = buffer + HDMI_INFOFRAME_HEADER_SIZE - 1;
+       u32 val;
+       u32 mask;
+       int ret;
+
+       DRM_DEBUG_DRIVER("\n");
+
+       ret = drm_hdmi_avi_infoframe_from_display_mode(&infoframe, mode);
+       if (ret < 0) {
+               DRM_ERROR("failed to setup AVI infoframe: %d\n", ret);
+               return ret;
+       }
+
+       /* TODO: remove static infoframe configuration */
+       infoframe.colorspace = HDMI_COLORSPACE_RGB;
+       infoframe.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT;
+       infoframe.colorimetry = HDMI_COLORIMETRY_NONE;
+       infoframe.pixel_repeat = 0;
+
+       ret = hdmi_avi_infoframe_pack(&infoframe, buffer, sizeof(buffer));
+       if (ret < 0) {
+               DRM_ERROR("failed to pack AVI infoframe: %d\n", ret);
+               return ret;
+       }
+
+       /* Disable transmission slot for AVI infoframe */
+       val = HDMI_IFRAME_DISABLED;
+       mask = HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, HDMI_IFRAME_SLOT_AVI);
+       hdmi_reg_writemask(hdmi->regs + HDMI_SW_DI_CFG, val, mask);
+
+       /* Infoframe header */
+       val = buffer[0x0];
+       val |= buffer[0x1] << 8;
+       val |= buffer[0x2] << 16;
+       writel(val, hdmi->regs + HDMI_SW_DI_N_HEAD_WORD(HDMI_IFRAME_SLOT_AVI));
+       /* Infoframe packet bytes */
+       val = frame[0x0];
+       val |= frame[0x1] << 8;
+       val |= frame[0x2] << 16;
+       val |= frame[0x3] << 24;
+       writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD0(HDMI_IFRAME_SLOT_AVI));
+       val = frame[0x4];
+       val |= frame[0x5] << 8;
+       val |= frame[0x6] << 16;
+       val |= frame[0x7] << 24;
+       writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD1(HDMI_IFRAME_SLOT_AVI));
+       val = frame[0x8];
+       val |= frame[0x9] << 8;
+       val |= frame[0xA] << 16;
+       val |= frame[0xB] << 24;
+       writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD2(HDMI_IFRAME_SLOT_AVI));
+       val = frame[0xC];
+       val |= frame[0xD] << 8;
+       writel(val, hdmi->regs + HDMI_SW_DI_N_PKT_WORD3(HDMI_IFRAME_SLOT_AVI));
+
+       /* Enable transmission slot for AVI infoframe */
+       /* According to the hdmi specification, AVI infoframe should be
+        * transmitted at least once per two video fields */
+       val = HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_FIELD, HDMI_IFRAME_SLOT_AVI);
+       mask = HDMI_IFRAME_CFG_DI_N(HDMI_IFRAME_MASK, HDMI_IFRAME_SLOT_AVI);
+       hdmi_reg_writemask(hdmi->regs + HDMI_SW_DI_CFG, val, mask);
+
+       return 0;
+}
+
+/*
+ * Software reset of the hdmi subsystem
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ *
+ * Return -1 if error occurs
+ */
+#define HDMI_TIMEOUT_SWRESET  100      /*milliseconds */
+static int hdmi_swreset(struct sti_hdmi *hdmi)
+{
+       u32 val;
+       u32 mask;
+
+       DRM_DEBUG_DRIVER("\n");
+
+       /* Enable hdmi_audio clock only during hdmi reset */
+       if (clk_prepare_enable(hdmi->clk_audio))
+               DRM_INFO("Failed to prepare/enable hdmi_audio clk\n");
+
+       /* Sw reset */
+       mask = HDMI_CFG_SW_RST_EN;
+       val = HDMI_CFG_SW_RST_EN;
+
+       hdmi->event_received = false;
+       hdmi_reg_writemask(hdmi->regs + HDMI_CFG, val, mask);
+
+       /* Wait reset completed */
+       wait_event_interruptible_timeout(hdmi->wait_event,
+                                        hdmi->event_received == true,
+                                        msecs_to_jiffies
+                                        (HDMI_TIMEOUT_SWRESET));
+
+       /*
+        * HDMI_STA_SW_RST bit is set to '1' when SW_RST bit in HDMI_CFG is
+        * set to '1' and clk_audio is running.
+        */
+       if ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_SW_RST) == 0)
+               DRM_INFO("Warning: HDMI sw reset timeout occurs\n");
+
+       mask = HDMI_CFG_SW_RST_EN;
+       val = ~HDMI_CFG_SW_RST_EN;
+       hdmi_reg_writemask(hdmi->regs + HDMI_CFG, val, mask);
+
+       /* Disable hdmi_audio clock. Not used anymore for drm purpose. */
+       clk_disable_unprepare(hdmi->clk_audio);
+
+       return 0;
+}
+
+/*
+ * Attach the I2C ddc client to allow hdmi i2c communication
+ *
+ * @ddc: i2c client
+ */
+static struct i2c_client *hdmi_ddc;
+void sti_hdmi_attach_ddc_client(struct i2c_client *ddc)
+{
+       DRM_DEBUG_DRIVER("\n");
+
+       if (ddc)
+               hdmi_ddc = ddc;
+}
+
+/*
+ * Get modes from edid
+ *
+ * @drm_connector: pointer on the drm connector
+ */
+static int sti_hdmi_get_modes(struct drm_connector *drm_connector)
+{
+       struct edid *edid;
+       int count;
+
+       DRM_DEBUG_DRIVER("\n");
+
+       if ((!hdmi_ddc) || (!hdmi_ddc->adapter))
+               goto fail;
+
+       edid = drm_get_edid(drm_connector, hdmi_ddc->adapter);
+       if (!edid)
+               goto fail;
+
+       count = drm_add_edid_modes(drm_connector, edid);
+       if (count)
+               drm_mode_connector_update_edid_property(drm_connector, edid);
+       else
+               DRM_ERROR("Add edid modes failed\n");
+
+       kfree(edid);
+       return count;
+
+fail:
+       DRM_ERROR("Can not read HDMI EDID\n");
+       return -1;
+}
+
+static int sti_hdmi_bind(struct device *dev, struct device *master, void *data)
+{
+       return 0;
+}
+
+static void sti_hdmi_unbind(struct device *dev, struct device *master,
+       void *data)
+{
+       /* do nothing */
+}
+
+static const struct component_ops sti_hdmi_ops = {
+       .bind   = sti_hdmi_bind,
+       .unbind = sti_hdmi_unbind,
+};
+
+static int sti_hdmi_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct sti_hdmi *hdmi;
+       struct device_node *np = dev->of_node;
+       struct resource *res;
+       int ret;
+
+       DRM_INFO("%s\n", __func__);
+
+       hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
+       if (!hdmi) {
+               DRM_ERROR("Failed to allocate memory for hdmi\n");
+               return -ENOMEM;
+       }
+
+       hdmi->dev = pdev->dev;
+
+       /* Get resources */
+       res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hdmi-reg");
+       if (!res) {
+               DRM_ERROR("Invalid hdmi resource\n");
+               return -ENOMEM;
+       }
+       hdmi->regs = devm_ioremap_nocache(dev, res->start, resource_size(res));
+       if (IS_ERR(hdmi->regs))
+               return PTR_ERR(hdmi->regs);
+
+       if (of_device_is_compatible(np, "st,stih416-hdmi")) {
+               res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+                                                  "syscfg");
+               if (!res) {
+                       DRM_ERROR("Invalid syscfg resource\n");
+                       return -ENOMEM;
+               }
+               hdmi->syscfg = devm_ioremap_nocache(dev, res->start,
+                                                   resource_size(res));
+               if (IS_ERR(hdmi->syscfg))
+                       return PTR_ERR(hdmi->syscfg);
+
+               hdmi->tx3g0c55phy = true;
+       }
+
+       /* Get clock resources */
+       hdmi->clk_pix = devm_clk_get(dev, "hdmi_pix");
+       if (IS_ERR(hdmi->clk_pix)) {
+               DRM_ERROR("Cannot get hdmi_pix clock\n");
+               return PTR_ERR(hdmi->clk_pix);
+       }
+
+       hdmi->clk_tmds = devm_clk_get(dev, "hdmi_tmds");
+       if (IS_ERR(hdmi->clk_tmds)) {
+               DRM_ERROR("Cannot get hdmi_tmds clock\n");
+               return PTR_ERR(hdmi->clk_tmds);
+       }
+
+       hdmi->clk_phy = devm_clk_get(dev, "hdmi_phy");
+       if (IS_ERR(hdmi->clk_phy)) {
+               DRM_ERROR("Cannot get hdmi_phy clock\n");
+               return PTR_ERR(hdmi->clk_phy);
+       }
+
+       hdmi->clk_audio = devm_clk_get(dev, "hdmi_audio");
+       if (IS_ERR(hdmi->clk_audio)) {
+               DRM_ERROR("Cannot get hdmi_audio clock\n");
+               return PTR_ERR(hdmi->clk_audio);
+       }
+
+       hdmi->hpd_gpio = of_get_named_gpio(np, "hdmi,hpd-gpio", 0);
+       if (hdmi->hpd_gpio < 0) {
+               DRM_ERROR("Failed to get hdmi hpd-gpio\n");
+               return -EIO;
+       }
+
+       hdmi->hpd = gpio_get_value(hdmi->hpd_gpio);
+
+       init_waitqueue_head(&hdmi->wait_event);
+
+       /* Get irq ressources */
+       hdmi->irq = platform_get_irq_byname(pdev, "hdmi_irq");
+
+       ret = devm_request_threaded_irq(dev, hdmi->irq, NULL,
+                                       hdmi_irq_thread, IRQF_ONESHOT,
+                                       "hdmi_irq", hdmi);
+       if (ret) {
+               DRM_ERROR("Failed to register hdmi interrupt\n");
+               return ret;
+       }
+
+       /* Get reset resources */
+       hdmi->reset = devm_reset_control_get(dev, "hdmi");
+       /* Take hdmi out of reset */
+       if (!IS_ERR(hdmi->reset))
+               reset_control_deassert(hdmi->reset);
+
+       hdmi_dev = &hdmi->dev;
+
+       platform_set_drvdata(pdev, hdmi);
+
+       return component_add(&pdev->dev, &sti_hdmi_ops);
+}
+
+static int sti_hdmi_remove(struct platform_device *pdev)
+{
+       component_del(&pdev->dev, &sti_hdmi_ops);
+       return 0;
+}
+
+static struct of_device_id hdmi_match_types[] = {
+       {
+        .compatible = "st,stih416-hdmi",
+        },
+       {
+        .compatible = "st,stih407-hdmi",
+        },
+       { /* end node */ }
+};
+MODULE_DEVICE_TABLE(of, hdmi_match_types);
+
+struct platform_driver sti_hdmi_driver = {
+       .driver = {
+                  .name = "sti-hdmi",
+                  .owner = THIS_MODULE,
+                  .of_match_table = hdmi_match_types,
+                  },
+       .probe = sti_hdmi_probe,
+       .remove = sti_hdmi_remove,
+};
+module_platform_driver(sti_hdmi_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/sti/sti_hdmi.h b/drivers/gpu/drm/sti/sti_hdmi.h
new file mode 100644
index 0000000..c14c683
--- /dev/null
+++ b/drivers/gpu/drm/sti/sti_hdmi.h
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2013
+ * Authors: Vincent Abriou <vincent.abriou at st.com> for STMicroelectronics.
+ * License terms:  GNU General Public License (GPL), version 2
+ */
+
+#ifndef _STI_HDMI_H_
+#define _STI_HDMI_H_
+
+#include <linux/platform_device.h>
+
+#include <drm/drmP.h>
+
+/* HDMI v2.9 macro cell */
+#define HDMI_CFG                        0x0000
+#define HDMI_INT_EN                     0x0004
+#define HDMI_INT_STA                    0x0008
+#define HDMI_INT_CLR                    0x000C
+#define HDMI_STA                        0x0010
+#define HDMI_ACTIVE_VID_XMIN            0x0100
+#define HDMI_ACTIVE_VID_XMAX            0x0104
+#define HDMI_ACTIVE_VID_YMIN            0x0108
+#define HDMI_ACTIVE_VID_YMAX            0x010C
+#define HDMI_DFLT_CHL0_DAT              0x0110
+#define HDMI_DFLT_CHL1_DAT              0x0114
+#define HDMI_DFLT_CHL2_DAT              0x0118
+#define HDMI_SW_DI_1_HEAD_WORD          0x0210
+#define HDMI_SW_DI_1_PKT_WORD0          0x0214
+#define HDMI_SW_DI_1_PKT_WORD1          0x0218
+#define HDMI_SW_DI_1_PKT_WORD2          0x021C
+#define HDMI_SW_DI_1_PKT_WORD3          0x0220
+#define HDMI_SW_DI_1_PKT_WORD4          0x0224
+#define HDMI_SW_DI_1_PKT_WORD5          0x0228
+#define HDMI_SW_DI_1_PKT_WORD6          0x022C
+#define HDMI_SW_DI_CFG                  0x0230
+
+#define HDMI_IFRAME_SLOT_AVI            1
+
+#define  XCAT(prefix, x, suffix)        prefix ## x ## suffix
+#define  HDMI_SW_DI_N_HEAD_WORD(x)      XCAT(HDMI_SW_DI_, x, _HEAD_WORD)
+#define  HDMI_SW_DI_N_PKT_WORD0(x)      XCAT(HDMI_SW_DI_, x, _PKT_WORD0)
+#define  HDMI_SW_DI_N_PKT_WORD1(x)      XCAT(HDMI_SW_DI_, x, _PKT_WORD1)
+#define  HDMI_SW_DI_N_PKT_WORD2(x)      XCAT(HDMI_SW_DI_, x, _PKT_WORD2)
+#define  HDMI_SW_DI_N_PKT_WORD3(x)      XCAT(HDMI_SW_DI_, x, _PKT_WORD3)
+#define  HDMI_SW_DI_N_PKT_WORD4(x)      XCAT(HDMI_SW_DI_, x, _PKT_WORD4)
+#define  HDMI_SW_DI_N_PKT_WORD5(x)      XCAT(HDMI_SW_DI_, x, _PKT_WORD5)
+#define  HDMI_SW_DI_N_PKT_WORD6(x)      XCAT(HDMI_SW_DI_, x, _PKT_WORD6)
+
+#define HDMI_IFRAME_DISABLED            0x0
+#define HDMI_IFRAME_SINGLE_SHOT         0x1
+#define HDMI_IFRAME_FIELD               0x2
+#define HDMI_IFRAME_FRAME               0x3
+#define HDMI_IFRAME_MASK                0x3
+#define HDMI_IFRAME_CFG_DI_N(x, n)      ((x) << ((n-1)*4)) /* n from 1 to 6 */
+
+#define HDMI_CFG_DEVICE_EN_SHIFT         0
+#define HDMI_CFG_DEVICE_EN               (1 << HDMI_CFG_DEVICE_EN_SHIFT)
+#define HDMI_CFG_HDMI_NOT_DVI_SHIFT      1
+#define HDMI_CFG_HDMI_NOT_DVI            (1 << HDMI_CFG_HDMI_NOT_DVI_SHIFT)
+#define HDMI_CFG_HDCP_EN_SHIFT           2
+#define HDMI_CFG_HDCP_EN                 (1 << HDMI_CFG_HDCP_EN_SHIFT)
+#define HDMI_CFG_ESS_NOT_OESS_SHIFT      3
+#define HDMI_CFG_ESS_NOT_OESS            (1 << HDMI_CFG_ESS_NOT_OESS_SHIFT)
+#define HDMI_CFG_H_SYNC_POL_NEG_SHIFT    4
+#define HDMI_CFG_H_SYNC_POL_NEG          (1 << HDMI_CFG_H_SYNC_POL_NEG_SHIFT)
+#define HDMI_CFG_SINK_TERM_DET_EN_SHIFT  5
+#define HDMI_CFG_SINK_TERM_DET_EN        (1 << HDMI_CFG_SINK_TERM_DET_EN_SHIFT)
+#define HDMI_CFG_V_SYNC_POL_NEG_SHIFT    6
+#define HDMI_CFG_V_SYNC_POL_NEG          (1 << HDMI_CFG_V_SYNC_POL_NEG_SHIFT)
+#define HDMI_CFG_422_EN_SHIFT            8
+#define HDMI_CFG_422_EN                  (1 << HDMI_CFG_422_EN_SHIFT)
+#define HDMI_CFG_FIFO_OVERRUN_CLR_SHIFT  12
+#define HDMI_CFG_FIFO_OVERRUN_CLR        (1 << HDMI_CFG_FIFO_OVERRUN_CLR_SHIFT)
+#define HDMI_CFG_FIFO_UNDERRUN_CLR_SHIFT 13
+#define HDMI_CFG_FIFO_UNDERRUN_CLR       (1 << 
HDMI_CFG_FIFO_UNDERRUN_CLR_SHIFT)
+#define HDMI_CFG_SW_RST_EN_SHIFT         31
+#define HDMI_CFG_SW_RST_EN               (1 << HDMI_CFG_SW_RST_EN_SHIFT)
+
+#define HDMI_INT_GLOBAL                        (1 << 0)
+#define HDMI_INT_SW_RST                 (1 << 1)
+#define HDMI_INT_IFRAME                 (1 << 2)
+#define HDMI_INT_PIX_CAP                (1 << 3)
+#define HDMI_INT_HOT_PLUG               (1 << 4)
+#define HDMI_INT_DLL_LCK                (1 << 5)
+#define HDMI_INT_NEW_FRAME              (1 << 6)
+#define HDMI_INT_GENCTRL_PKT            (1 << 7)
+#define HDMI_INT_SPDIF_FIFO_OVERRUN     (1 << 8)
+#define HDMI_INT_VID_FIFO_UNDERRUN      (1 << 9)
+#define HDMI_INT_VID_FIFO_OVERRUN       (1 << 10)
+#define HDMI_INT_SINK_TERM_PRESENT      (1 << 11)
+#define HDMI_INT_DI_2                   (1 << 16)
+#define HDMI_INT_DI_3                   (1 << 17)
+#define HDMI_INT_DI_4                   (1 << 18)
+#define HDMI_INT_DI_5                   (1 << 19)
+#define HDMI_INT_DI_6                   (1 << 20)
+#define HDMI_INT_DI_DMA_VSYNC_DONE      (1 << 21)
+#define HDMI_INT_DI_VSYNC_DONE          (1 << 22)
+
+#define HDMI_DEFAULT_INT (HDMI_INT_SINK_TERM_PRESENT \
+                       | HDMI_INT_DLL_LCK \
+                       | HDMI_INT_HOT_PLUG \
+                       | HDMI_INT_GLOBAL)
+
+#define HDMI_WORKING_INT (HDMI_INT_SINK_TERM_PRESENT \
+                       | HDMI_INT_GENCTRL_PKT \
+                       | HDMI_INT_NEW_FRAME \
+                       | HDMI_INT_DLL_LCK \
+                       | HDMI_INT_HOT_PLUG \
+                       | HDMI_INT_PIX_CAP \
+                       | HDMI_INT_SW_RST \
+                       | HDMI_INT_GLOBAL)
+
+#define HDMI_STA_SW_RST_SHIFT           1
+#define HDMI_STA_SW_RST                 (1 << HDMI_STA_SW_RST_SHIFT)
+#define HDMI_STA_PIX_CAP_SHIFT          3
+#define HDMI_STA_PIX_CAP                (1 << HDMI_STA_PIX_CAP_SHIFT)
+#define HDMI_STA_HOT_PLUG_SHIFT         4
+#define HDMI_STA_HOT_PLUG               (1 << HDMI_STA_HOT_PLUG_SHIFT)
+#define HDMI_STA_DLL_LCK_SHIFT          5
+#define HDMI_STA_DLL_LCK                (1 << HDMI_STA_DLL_LCK_SHIFT)
+#define HDMI_STA_SINK_TERM_SHIFT        6
+#define HDMI_STA_SINK_TERM              (1 << HDMI_STA_SINK_TERM_SHIFT)
+#define HDMI_STA_FIFO_SAMPLES_SHIFT     8
+#define HDMI_STA_FIFO_SAMPLES           (0x1F << HDMI_STA_FIFO_SAMPLES_SHIFT)
+
+/*
+ * STI hdmi structure
+ *
+ * @dev: driver device
+ * @drm_dev: pointer to drm device
+ * @mode: current display mode selected
+ * @regs: hdmi register
+ * @syscfg: syscfg register for pll rejection configuration
+ * @clk_pix: hdmi pixel clock
+ * @clk_tmds: hdmi tmds clock
+ * @clk_phy: hdmi phy clock
+ * @clk_audio: hdmi audio clock
+ * @irq: hdmi interrupt number
+ * @tx3g0c55phy: true if 3g0c55phy is supported
+ * @enabled: true if hdmi is enabled else false
+ * @hpd_gpio: hdmi hot plug detect gpio number
+ * @hpd: hot plug detect status
+ * @wait_event: wait event
+ * @event_received: wait event status
+ * @reset: reset control of the hdmi phy
+ */
+struct sti_hdmi {
+       struct device dev;
+       struct drm_device *drm_dev;
+       struct drm_display_mode mode;
+       void __iomem *regs;
+       void __iomem *syscfg;
+       struct clk *clk_pix;
+       struct clk *clk_tmds;
+       struct clk *clk_phy;
+       struct clk *clk_audio;
+       int irq;
+       bool tx3g0c55phy;
+       bool enabled;
+       int hpd_gpio;
+       bool hpd;
+       wait_queue_head_t wait_event;
+       bool event_received;
+       struct reset_control *reset;
+};
+
+/* hdmi phy config structure
+ *
+ * A pointer to an array of these structures is passed to a TMDS (HDMI) output
+ * via the control interface to provide board and SoC specific
+ * configurations of the HDMI PHY. Each entry in the array specifies a hardware
+ * specific configuration for a given TMDS clock frequency range.
+ *
+ * @min_tmds_freq: Lower bound of TMDS clock frequency this entry applies to
+ * @max_tmds_freq: Upper bound of TMDS clock frequency this entry applies to
+ * @config: SoC specific register configuration
+ */
+struct hdmi_phy_config {
+       u32 min_tmds_freq;
+       u32 max_tmds_freq;
+       u32 config[4];
+};
+
+void sti_hdmi_attach_ddc_client(struct i2c_client *ddc);
+
+int sti_hdmi_tx3g0c55phy_start(struct sti_hdmi *hdmi);
+void sti_hdmi_tx3g0c55phy_stop(struct sti_hdmi *hdmi);
+void sti_hdmi_tx3g0c55phy_show(struct sti_hdmi *hdmi, struct seq_file *m);
+
+int sti_hdmi_tx3g4c28phy_start(struct sti_hdmi *hdmi);
+void sti_hdmi_tx3g4c28phy_stop(struct sti_hdmi *hdmi);
+void sti_hdmi_tx3g4c28phy_show(struct sti_hdmi *hdmi, struct seq_file *m);
+
+extern struct i2c_driver ddc_driver;
+#endif
diff --git a/drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c 
b/drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c
new file mode 100644
index 0000000..547e9ee
--- /dev/null
+++ b/drivers/gpu/drm/sti/sti_hdmi_tx3g0c55phy.c
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2013
+ * Author: Vincent Abriou <vincent.abriou at st.com> for STMicroelectronics.
+ * License terms:  GNU General Public License (GPL), version 2
+ */
+
+#include "sti_hdmi.h"
+
+#define HDMI_SRZ_PLL_CFG                0x0504
+#define HDMI_SRZ_TAP_1                  0x0508
+#define HDMI_SRZ_TAP_2                  0x050C
+#define HDMI_SRZ_TAP_3                  0x0510
+#define HDMI_SRZ_CTRL                   0x0514
+
+#define HDMI_SRZ_PLL_CFG_POWER_DOWN     (1 << 0)
+#define HDMI_SRZ_PLL_CFG_VCOR_SHIFT     1
+#define HDMI_SRZ_PLL_CFG_VCOR_425MHZ    0
+#define HDMI_SRZ_PLL_CFG_VCOR_850MHZ    1
+#define HDMI_SRZ_PLL_CFG_VCOR_1700MHZ   2
+#define HDMI_SRZ_PLL_CFG_VCOR_3000MHZ   3
+#define HDMI_SRZ_PLL_CFG_VCOR_MASK      3
+#define HDMI_SRZ_PLL_CFG_VCOR(x)        (x << HDMI_SRZ_PLL_CFG_VCOR_SHIFT)
+#define HDMI_SRZ_PLL_CFG_NDIV_SHIFT     8
+#define HDMI_SRZ_PLL_CFG_NDIV_MASK      (0x1F << HDMI_SRZ_PLL_CFG_NDIV_SHIFT)
+#define HDMI_SRZ_PLL_CFG_MODE_SHIFT     16
+#define HDMI_SRZ_PLL_CFG_MODE_13_5_MHZ  0x1
+#define HDMI_SRZ_PLL_CFG_MODE_25_2_MHZ  0x4
+#define HDMI_SRZ_PLL_CFG_MODE_27_MHZ    0x5
+#define HDMI_SRZ_PLL_CFG_MODE_33_75_MHZ 0x6
+#define HDMI_SRZ_PLL_CFG_MODE_40_5_MHZ  0x7
+#define HDMI_SRZ_PLL_CFG_MODE_54_MHZ    0x8
+#define HDMI_SRZ_PLL_CFG_MODE_67_5_MHZ  0x9
+#define HDMI_SRZ_PLL_CFG_MODE_74_25_MHZ 0xA
+#define HDMI_SRZ_PLL_CFG_MODE_81_MHZ    0xB
+#define HDMI_SRZ_PLL_CFG_MODE_82_5_MHZ  0xC
+#define HDMI_SRZ_PLL_CFG_MODE_108_MHZ   0xD
+#define HDMI_SRZ_PLL_CFG_MODE_148_5_MHZ 0xE
+#define HDMI_SRZ_PLL_CFG_MODE_165_MHZ   0xF
+#define HDMI_SRZ_PLL_CFG_MODE_MASK      0xF
+#define HDMI_SRZ_PLL_CFG_MODE(x)        (x << HDMI_SRZ_PLL_CFG_MODE_SHIFT)
+
+#define HDMI_SRZ_CTRL_POWER_DOWN        (1 << 0)
+#define HDMI_SRZ_CTRL_EXTERNAL_DATA_EN  (1 << 1)
+
+/* sysconf registers */
+#define HDMI_REJECTION_PLL_CONFIGURATION 0x0858        /* SYSTEM_CONFIG2534 */
+#define HDMI_REJECTION_PLL_STATUS        0x0948        /* SYSTEM_CONFIG2594 */
+
+#define REJECTION_PLL_HDMI_ENABLE_SHIFT 0
+#define REJECTION_PLL_HDMI_ENABLE_MASK  (0x1 << 
REJECTION_PLL_HDMI_ENABLE_SHIFT)
+#define REJECTION_PLL_HDMI_PDIV_SHIFT   24
+#define REJECTION_PLL_HDMI_PDIV_MASK    (0x7 << REJECTION_PLL_HDMI_PDIV_SHIFT)
+#define REJECTION_PLL_HDMI_NDIV_SHIFT   16
+#define REJECTION_PLL_HDMI_NDIV_MASK    (0xFF << REJECTION_PLL_HDMI_NDIV_SHIFT)
+#define REJECTION_PLL_HDMI_MDIV_SHIFT   8
+#define REJECTION_PLL_HDMI_MDIV_MASK    (0xFF << REJECTION_PLL_HDMI_MDIV_SHIFT)
+
+#define REJECTION_PLL_HDMI_REJ_PLL_LOCK (0x1 << 0)
+
+#define HDMI_TIMEOUT_PLL_LOCK  50      /*milliseconds */
+
+#define HDMI_WAIT_PLL_REJECTION_STATUS 1000
+
+/* pll mode structure
+ *
+ * A pointer to an array of these structures is passed to a TMDS (HDMI) output
+ * via the control interface to provide board and SoC specific
+ * configurations of the HDMI PHY. Each entry in the array specifies a hardware
+ * specific configuration for a given TMDS clock frequency range. The array
+ * should be terminated with an entry that has all fields set to zero.
+ *
+ * @min: Lower bound of TMDS clock frequency this entry applies to
+ * @max: Upper bound of TMDS clock frequency this entry applies to
+ * @mode: SoC specific register configuration
+ */
+struct pllmode {
+       u32 min;
+       u32 max;
+       u32 mode;
+};
+#define NB_PLL_MODE 7
+static struct pllmode pllmodes[NB_PLL_MODE] = {
+       {13500000, 13513500, HDMI_SRZ_PLL_CFG_MODE_13_5_MHZ},
+       {25174800, 25200000, HDMI_SRZ_PLL_CFG_MODE_25_2_MHZ},
+       {27000000, 27027000, HDMI_SRZ_PLL_CFG_MODE_27_MHZ},
+       {54000000, 54054000, HDMI_SRZ_PLL_CFG_MODE_54_MHZ},
+       {72000000, 74250000, HDMI_SRZ_PLL_CFG_MODE_74_25_MHZ},
+       {108000000, 108108000, HDMI_SRZ_PLL_CFG_MODE_108_MHZ},
+       {148351648, 297000000, HDMI_SRZ_PLL_CFG_MODE_148_5_MHZ}
+};
+
+#define NB_HDMI_PHY_CONFIG 5
+static struct hdmi_phy_config hdmiphy_config[NB_HDMI_PHY_CONFIG] = {
+       {0, 40000000, {0x00101010, 0x00101010, 0x00101010, 0x02} },
+       {40000000, 140000000, {0x00111111, 0x00111111, 0x00111111, 0x02} },
+       {140000000, 160000000, {0x00131313, 0x00101010, 0x00101010, 0x02} },
+       {160000000, 250000000, {0x00131313, 0x00111111, 0x00111111, 0x03FE} },
+       {250000000, 300000000, {0x00151515, 0x00101010, 0x00101010, 0x03FE} },
+};
+
+/*
+ * Helper to write bit field
+ *
+ * @addr: register to update
+ * @val: value to write
+ * @mask: bit field mask to use
+ */
+static inline void reg_writemask(void __iomem *addr, u32 val, u32 mask)
+{
+       u32 old = readl(addr);
+
+       val = (val & mask) | (old & ~mask);
+       writel(val, addr);
+}
+
+/*
+ * Enable the old BCH/rejection PLL is now reused to provide the CLKPXPLL
+ * clock input to the new PHY PLL that generates the serializer clock
+ * (TMDS*10) and the TMDS clock which is now fed back into the HDMI
+ * formatter instead of the TMDS clock line from ClockGenB.
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ *
+ * Return -1 if error occurs
+ */
+static int enable_pll_rejection(struct sti_hdmi *hdmi)
+{
+       int inputclock;
+       u32 mdiv;
+       u32 ndiv;
+       u32 pdiv;
+       u32 mask;
+       u32 val;
+       int i;
+
+       DRM_DEBUG_DRIVER("\n");
+
+       inputclock = hdmi->mode.clock * 1000;
+
+       DRM_DEBUG_DRIVER("hdmi rejection pll input clock = %dHz\n", inputclock);
+
+       /* Force to power down the HDMI rejection PLL */
+       mask = REJECTION_PLL_HDMI_ENABLE_MASK;
+       val = 0x0 << REJECTION_PLL_HDMI_ENABLE_SHIFT;
+       reg_writemask(hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION,
+                     val, mask);
+
+       /* Check the HDMI rejection PLL is really down */
+       for (i = 0; i < HDMI_WAIT_PLL_REJECTION_STATUS; i++) {
+               val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_STATUS);
+               if ((val & REJECTION_PLL_HDMI_REJ_PLL_LOCK) == 0)
+                       break;
+       }
+       if (i == HDMI_WAIT_PLL_REJECTION_STATUS) {
+               DRM_ERROR("hdmi rejection pll is not well powered down\n");
+               return -1;
+       }
+
+       /* Power up the HDMI rejection PLL */
+       /*
+        * Note: On this SoC (stiH416) we are forced to have the input clock
+        * be equal to the HDMI pixel clock.
+        *
+        * The values here have been suggested by validation however they are
+        * still provisional and subject to change.
+        *
+        * PLLout = (Fin*Mdiv) / ((2 * Ndiv) / 2^Pdiv)
+        */
+       if (inputclock < 50000000) {
+               /*
+                * For slower clocks we need to multiply more to keep the
+                * internal VCO frequency within the physical specification
+                * of the PLL.
+                */
+               pdiv = 4;
+               ndiv = 240;
+               mdiv = 30;
+       } else {
+               pdiv = 2;
+               ndiv = 60;
+               mdiv = 30;
+       }
+
+       mask = REJECTION_PLL_HDMI_PDIV_MASK |
+           REJECTION_PLL_HDMI_NDIV_MASK |
+           REJECTION_PLL_HDMI_MDIV_MASK | REJECTION_PLL_HDMI_ENABLE_MASK;
+       val = (pdiv << REJECTION_PLL_HDMI_PDIV_SHIFT) |
+           (ndiv << REJECTION_PLL_HDMI_NDIV_SHIFT) |
+           (mdiv << REJECTION_PLL_HDMI_MDIV_SHIFT) |
+           (0x1 << REJECTION_PLL_HDMI_ENABLE_SHIFT);
+       reg_writemask(hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION,
+                     val, mask);
+
+       /* Check the HDMI rejection PLL is really up */
+       for (i = 0; i < HDMI_WAIT_PLL_REJECTION_STATUS; i++) {
+               val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_STATUS);
+               if ((val & REJECTION_PLL_HDMI_REJ_PLL_LOCK) != 0)
+                       break;
+       }
+       if (i == HDMI_WAIT_PLL_REJECTION_STATUS) {
+               DRM_ERROR("hdmi rejection pll is not well powered up\n");
+               return -1;
+       }
+
+       DRM_DEBUG_DRIVER("hdmi rejection pll locked\n");
+
+       return 0;
+}
+
+/*
+ * Disable the pll rejection
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ */
+static void disable_pll_rejection(struct sti_hdmi *hdmi)
+{
+       int i;
+       u32 val;
+       u32 mask;
+
+       DRM_DEBUG_DRIVER("\n");
+
+       mask = REJECTION_PLL_HDMI_ENABLE_MASK;
+       val = 0x0 << REJECTION_PLL_HDMI_ENABLE_SHIFT;
+       reg_writemask(hdmi->syscfg + HDMI_REJECTION_PLL_CONFIGURATION,
+                     val, mask);
+
+       /* Check the HDMI rejection PLL is really down */
+       for (i = 0; i < HDMI_WAIT_PLL_REJECTION_STATUS; i++) {
+               val = readl(hdmi->syscfg + HDMI_REJECTION_PLL_STATUS);
+               if ((val & REJECTION_PLL_HDMI_REJ_PLL_LOCK) == 0)
+                       break;
+       }
+       if (i == HDMI_WAIT_PLL_REJECTION_STATUS)
+               DRM_ERROR("hdmi rejection pll is not well powered down\n");
+       else
+               DRM_DEBUG_DRIVER("hdmi rejection pll is powered down\n");
+}
+
+/*
+ * Start hdmi phy macro cell tx3g0c55
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ *
+ * Return -1 if error occurs
+ */
+int sti_hdmi_tx3g0c55phy_start(struct sti_hdmi *hdmi)
+{
+       u32 ckpxpll = hdmi->mode.clock * 1000;
+       u32 tmdsck;
+       u32 freqvco;
+       u32 pllctrl = 0;
+       u32 val;
+       int i;
+
+       if (enable_pll_rejection(hdmi))
+               return -1;
+
+       DRM_DEBUG_DRIVER("ckpxpll = %dHz\n", ckpxpll);
+
+       /* TODO: manage DeepColor (30, 36 and 48 bits) and pixel repetition */
+
+       /* Assuming no pixel repetition and 24bits color */
+       tmdsck = ckpxpll;
+       pllctrl = 2 << HDMI_SRZ_PLL_CFG_NDIV_SHIFT;
+
+       /*
+        * Setup the PLL mode parameter based on the ckpxpll. If we haven't got
+        * a clock frequency supported by one of the specific PLL modes then we
+        * will end up using the generic mode (0) which only supports a 10x
+        * multiplier, hence only 24bit color.
+        */
+       for (i = 0; i < NB_PLL_MODE; i++) {
+               if (ckpxpll >= pllmodes[i].min && ckpxpll <= pllmodes[i].max)
+                       pllctrl |= HDMI_SRZ_PLL_CFG_MODE(pllmodes[i].mode);
+       }
+
+       freqvco = tmdsck * 10;
+       if (freqvco <= 425000000UL)
+               pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_425MHZ);
+       else if (freqvco <= 850000000UL)
+               pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_850MHZ);
+       else if (freqvco <= 1700000000UL)
+               pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_1700MHZ);
+       else if (freqvco <= 2970000000UL)
+               pllctrl |= HDMI_SRZ_PLL_CFG_VCOR(HDMI_SRZ_PLL_CFG_VCOR_3000MHZ);
+       else {
+               DRM_ERROR("PHY serializer clock out of range\n");
+               goto err;
+       }
+
+       /*
+        * Configure and power up the PHY PLL
+        */
+       hdmi->event_received = false;
+       DRM_DEBUG_DRIVER("pllctrl = 0x%x\n", pllctrl);
+       writel(pllctrl, hdmi->regs + HDMI_SRZ_PLL_CFG);
+
+       /* wait PLL interrupt */
+       wait_event_interruptible_timeout(hdmi->wait_event,
+                                        hdmi->event_received == true,
+                                        msecs_to_jiffies
+                                        (HDMI_TIMEOUT_PLL_LOCK));
+
+       if ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_DLL_LCK) == 0) {
+               DRM_ERROR("hdmi phy pll not locked\n");
+               goto err;
+       }
+
+       DRM_DEBUG_DRIVER("got PHY PLL Lock\n");
+
+       /*
+        * To configure the source termination and pre-emphasis appropriately
+        * for different high speed TMDS clock frequencies a phy configuration
+        * table must be provided, tailored to the SoC and board combination.
+        */
+       for (i = 0; i < NB_HDMI_PHY_CONFIG; i++) {
+               if ((hdmiphy_config[i].min_tmds_freq <= tmdsck) &&
+                   (hdmiphy_config[i].max_tmds_freq >= tmdsck)) {
+                       val = hdmiphy_config[i].config[0];
+                       writel(val, hdmi->regs + HDMI_SRZ_TAP_1);
+                       val = hdmiphy_config[i].config[1];
+                       writel(val, hdmi->regs + HDMI_SRZ_TAP_2);
+                       val = hdmiphy_config[i].config[2];
+                       writel(val, hdmi->regs + HDMI_SRZ_TAP_3);
+                       val = hdmiphy_config[i].config[3];
+                       val |= HDMI_SRZ_CTRL_EXTERNAL_DATA_EN;
+                       val &= ~HDMI_SRZ_CTRL_POWER_DOWN;
+                       writel(val, hdmi->regs + HDMI_SRZ_CTRL);
+
+                       DRM_DEBUG_DRIVER("serializer cfg 0x%x 0x%x 0x%x 0x%x\n",
+                                        hdmiphy_config[i].config[0],
+                                        hdmiphy_config[i].config[1],
+                                        hdmiphy_config[i].config[2],
+                                        hdmiphy_config[i].config[3]);
+                       return 0;
+               }
+       }
+
+       /*
+        * Default, power up the serializer with no pre-emphasis or source
+        * termination.
+        */
+       writel(0x0, hdmi->regs + HDMI_SRZ_TAP_1);
+       writel(0x0, hdmi->regs + HDMI_SRZ_TAP_2);
+       writel(0x0, hdmi->regs + HDMI_SRZ_TAP_3);
+       writel(HDMI_SRZ_CTRL_EXTERNAL_DATA_EN, hdmi->regs + HDMI_SRZ_CTRL);
+
+       return 0;
+
+err:
+       disable_pll_rejection(hdmi);
+
+       return -1;
+}
+
+/*
+ * Stop hdmi phy macro cell tx3g0c55
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ */
+void sti_hdmi_tx3g0c55phy_stop(struct sti_hdmi *hdmi)
+{
+       DRM_DEBUG_DRIVER("\n");
+
+       hdmi->event_received = false;
+
+       writel(HDMI_SRZ_CTRL_POWER_DOWN, hdmi->regs + HDMI_SRZ_CTRL);
+       writel(HDMI_SRZ_PLL_CFG_POWER_DOWN, hdmi->regs + HDMI_SRZ_PLL_CFG);
+
+       /* wait PLL interrupt */
+       wait_event_interruptible_timeout(hdmi->wait_event,
+                                        hdmi->event_received == true,
+                                        msecs_to_jiffies
+                                        (HDMI_TIMEOUT_PLL_LOCK));
+
+       if ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_DLL_LCK) == 1)
+               DRM_ERROR("hdmi phy pll not well disabled\n");
+       else
+               DRM_DEBUG_DRIVER("hdmi phy pll disabled\n");
+
+       disable_pll_rejection(hdmi);
+}
+
+/*
+ * Debugfs
+ */
+#define HDMI_DBG_DUMP(reg) seq_printf(m, "\n %-25s 0x%08X", #reg, \
+               readl(hdmi->regs + reg))
+void sti_hdmi_tx3g0c55phy_show(struct sti_hdmi *hdmi, struct seq_file *m)
+{
+       HDMI_DBG_DUMP(HDMI_SRZ_PLL_CFG);
+       HDMI_DBG_DUMP(HDMI_SRZ_TAP_1);
+       HDMI_DBG_DUMP(HDMI_SRZ_TAP_2);
+       HDMI_DBG_DUMP(HDMI_SRZ_TAP_3);
+       HDMI_DBG_DUMP(HDMI_SRZ_CTRL);
+       seq_puts(m, "\n");
+}
diff --git a/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c 
b/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c
new file mode 100644
index 0000000..6e0bc2c
--- /dev/null
+++ b/drivers/gpu/drm/sti/sti_hdmi_tx3g4c28phy.c
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2013
+ * Author: Vincent Abriou <vincent.abriou at st.com> for STMicroelectronics.
+ * License terms:  GNU General Public License (GPL), version 2
+ */
+
+#include "sti_hdmi.h"
+
+#define HDMI_SRZ_CFG                    0x504
+#define HDMI_SRZ_PLL_CFG                0x510
+#define HDMI_SRZ_ICNTL                  0x518
+#define HDMI_SRZ_CALCODE_EXT            0x520
+
+#define HDMI_SRZ_CFG_EN                          (1L<<0)
+#define HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT (1L<<1)
+#define HDMI_SRZ_CFG_EXTERNAL_DATA               (1L<<16)
+#define HDMI_SRZ_CFG_RBIAS_EXT                   (1L<<17)
+#define HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION      (1L<<18)
+#define HDMI_SRZ_CFG_EN_BIASRES_DETECTION        (1L<<19)
+#define HDMI_SRZ_CFG_EN_SRC_TERMINATION          (1L<<24)
+
+#define HDMI_SRZ_CFG_INTERNAL_MASK  (HDMI_SRZ_CFG_EN     | \
+               HDMI_SRZ_CFG_DISABLE_BYPASS_SINK_CURRENT | \
+               HDMI_SRZ_CFG_EXTERNAL_DATA               | \
+               HDMI_SRZ_CFG_RBIAS_EXT                   | \
+               HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION      | \
+               HDMI_SRZ_CFG_EN_BIASRES_DETECTION        | \
+               HDMI_SRZ_CFG_EN_SRC_TERMINATION)
+
+#define PLL_CFG_EN         (1L<<0)
+#define PLL_CFG_NDIV_SHIFT (8)
+#define PLL_CFG_IDF_SHIFT  (16)
+#define PLL_CFG_ODF_SHIFT  (24)
+
+#define ODF_DIV_1          (0)
+#define ODF_DIV_2          (1)
+#define ODF_DIV_4          (2)
+#define ODF_DIV_8          (3)
+
+#define HDMI_TIMEOUT_PLL_LOCK  50      /*milliseconds */
+
+struct plldividers_s {
+       uint32_t min;
+       uint32_t max;
+       uint32_t idf;
+       uint32_t odf;
+};
+
+/*
+ * Functional specification recommended values
+ */
+#define NB_PLL_MODE 5
+static struct plldividers_s plldividers[NB_PLL_MODE] = {
+       {0, 20000000, 1, ODF_DIV_8},
+       {20000000, 42500000, 2, ODF_DIV_8},
+       {42500000, 85000000, 4, ODF_DIV_4},
+       {85000000, 170000000, 8, ODF_DIV_2},
+       {170000000, 340000000, 16, ODF_DIV_1}
+};
+
+#define NB_HDMI_PHY_CONFIG 2
+static struct hdmi_phy_config hdmiphy_config[NB_HDMI_PHY_CONFIG] = {
+       {0, 250000000, {0x0, 0x0, 0x0, 0x0} },
+       {250000000, 300000000, {0x1110, 0x0, 0x0, 0x0} },
+};
+
+/*
+ * Start hdmi phy macro cell tx3g4c28
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ *
+ * Return -1 if error occurs
+ */
+int sti_hdmi_tx3g4c28phy_start(struct sti_hdmi *hdmi)
+{
+       u32 ckpxpll = hdmi->mode.clock * 1000;
+       u32 tmdsck;
+       u32 idf;
+       u32 odf;
+       u32 pllctrl = 0;
+       u32 val;
+       int i;
+       bool foundplldivides = false;
+
+       DRM_DEBUG_DRIVER("ckpxpll = %dHz\n", ckpxpll);
+
+       for (i = 0; i < NB_PLL_MODE; i++) {
+               if (ckpxpll >= plldividers[i].min &&
+                   ckpxpll < plldividers[i].max) {
+                       idf = plldividers[i].idf;
+                       odf = plldividers[i].odf;
+                       foundplldivides = true;
+                       break;
+               }
+       }
+
+       if (!foundplldivides) {
+               DRM_ERROR("input TMDS clock speed (%d) not supported\n",
+                         ckpxpll);
+               goto err;
+       }
+
+       /* TODO: manage DeepColor (30, 36 and 48 bits) and pixel repetition */
+
+       /* Assuming no pixel repetition and 24bits color */
+       tmdsck = ckpxpll;
+       pllctrl |= 40 << PLL_CFG_NDIV_SHIFT;
+
+       if (tmdsck > 340000000) {
+               DRM_ERROR("output TMDS clock (%d) out of range\n", tmdsck);
+               goto err;
+       }
+
+       pllctrl |= idf << PLL_CFG_IDF_SHIFT;
+       pllctrl |= odf << PLL_CFG_ODF_SHIFT;
+
+       /*
+        * Configure and power up the PHY PLL
+        */
+       hdmi->event_received = false;
+       DRM_DEBUG_DRIVER("pllctrl = 0x%x\n", pllctrl);
+       writel((pllctrl | PLL_CFG_EN), hdmi->regs + HDMI_SRZ_PLL_CFG);
+
+       /* wait PLL interrupt */
+       wait_event_interruptible_timeout(hdmi->wait_event,
+                                        hdmi->event_received == true,
+                                        msecs_to_jiffies
+                                        (HDMI_TIMEOUT_PLL_LOCK));
+
+       if ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_DLL_LCK) == 0) {
+               DRM_ERROR("hdmi phy pll not locked\n");
+               goto err;
+       }
+
+       DRM_DEBUG_DRIVER("got PHY PLL Lock\n");
+
+       val = (HDMI_SRZ_CFG_EN |
+              HDMI_SRZ_CFG_EXTERNAL_DATA |
+              HDMI_SRZ_CFG_EN_BIASRES_DETECTION |
+              HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION);
+
+       if (tmdsck > 165000000)
+               val |= HDMI_SRZ_CFG_EN_SRC_TERMINATION;
+
+       /*
+        * To configure the source termination and pre-emphasis appropriately
+        * for different high speed TMDS clock frequencies a phy configuration
+        * table must be provided, tailored to the SoC and board combination.
+        */
+       for (i = 0; i < NB_HDMI_PHY_CONFIG; i++) {
+               if ((hdmiphy_config[i].min_tmds_freq <= tmdsck) &&
+                   (hdmiphy_config[i].max_tmds_freq >= tmdsck)) {
+                       val |= (hdmiphy_config[i].config[0]
+                               & ~HDMI_SRZ_CFG_INTERNAL_MASK);
+                       writel(val, hdmi->regs + HDMI_SRZ_CFG);
+                       val = hdmiphy_config[i].config[1];
+                       writel(val, hdmi->regs + HDMI_SRZ_ICNTL);
+                       val = hdmiphy_config[i].config[2];
+                       writel(val, hdmi->regs + HDMI_SRZ_CALCODE_EXT);
+                       DRM_DEBUG_DRIVER("serializer cfg 0x%x 0x%x 0x%x\n",
+                                        hdmiphy_config[i].config[0],
+                                        hdmiphy_config[i].config[1],
+                                        hdmiphy_config[i].config[2]);
+                       return 0;
+               }
+       }
+
+       /*
+        * Default, power up the serializer with no pre-emphasis or
+        * output swing correction
+        */
+       writel(val, hdmi->regs + HDMI_SRZ_CFG);
+       writel(0x0, hdmi->regs + HDMI_SRZ_ICNTL);
+       writel(0x0, hdmi->regs + HDMI_SRZ_CALCODE_EXT);
+
+       return 0;
+
+err:
+       return -1;
+}
+
+/*
+ * Stop hdmi phy macro cell tx3g4c28
+ *
+ * @hdmi: pointer on the hdmi internal structure
+ */
+void sti_hdmi_tx3g4c28phy_stop(struct sti_hdmi *hdmi)
+{
+       int val = 0;
+
+       DRM_DEBUG_DRIVER("\n");
+
+       hdmi->event_received = false;
+
+       val = HDMI_SRZ_CFG_EN_SINK_TERM_DETECTION;
+       val |= HDMI_SRZ_CFG_EN_BIASRES_DETECTION;
+       writel(val, hdmi->regs + HDMI_SRZ_CFG);
+       writel(0, hdmi->regs + HDMI_SRZ_PLL_CFG);
+
+       /* wait PLL interrupt */
+       wait_event_interruptible_timeout(hdmi->wait_event,
+                                        hdmi->event_received == true,
+                                        msecs_to_jiffies
+                                        (HDMI_TIMEOUT_PLL_LOCK));
+
+       if ((readl(hdmi->regs + HDMI_STA) & HDMI_STA_DLL_LCK) == 1)
+               DRM_ERROR("hdmi phy pll not well disabled\n");
+       else
+               DRM_DEBUG_DRIVER("hdmi phy pll disabled\n");
+}
+
+/*
+ * Debugfs
+ */
+#define HDMI_DBG_DUMP(reg) seq_printf(m, "\n %-25s 0x%08X", #reg, \
+               readl(hdmi->regs + reg))
+void sti_hdmi_tx3g4c28phy_show(struct sti_hdmi *hdmi, struct seq_file *m)
+{
+       HDMI_DBG_DUMP(HDMI_SRZ_CFG);
+       HDMI_DBG_DUMP(HDMI_SRZ_PLL_CFG);
+       HDMI_DBG_DUMP(HDMI_SRZ_ICNTL);
+       HDMI_DBG_DUMP(HDMI_SRZ_CALCODE_EXT);
+       seq_puts(m, "\n");
+}
-- 
1.9.1

Reply via email to